Merge branch 'develop' into fix/experience
@ -10,6 +10,7 @@
|
|||||||
"commit": false,
|
"commit": false,
|
||||||
"commitConvention": "angular",
|
"commitConvention": "angular",
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
"badgeTemplate": "\n[all-contributors-badge]: https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square\n",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
"login": "darkskygit",
|
"login": "darkskygit",
|
||||||
@ -229,6 +230,42 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"doc"
|
"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: [
|
scopes: [
|
||||||
{ name: 'selection' },
|
{ name: 'selection' },
|
||||||
{ name: 'whiteboard' },
|
{ name: 'edgeless' },
|
||||||
{ name: 'point' },
|
{ name: 'point' },
|
||||||
{ name: 'group' },
|
{ name: 'group' },
|
||||||
{ name: 'page' },
|
{ name: 'page' },
|
||||||
|
1
.env
@ -1,4 +1,5 @@
|
|||||||
# use for download icon from figma
|
# use for download icon from figma
|
||||||
|
|
||||||
FIGMA_TOKEN
|
FIGMA_TOKEN
|
||||||
NODE_ENV
|
NODE_ENV
|
||||||
AFFINE_FEATURE_FLAG_TOKEN
|
AFFINE_FEATURE_FLAG_TOKEN
|
||||||
|
1
.vscode/settings.json
vendored
@ -25,6 +25,7 @@
|
|||||||
"Kanban",
|
"Kanban",
|
||||||
"keyval",
|
"keyval",
|
||||||
"ligo",
|
"ligo",
|
||||||
|
"livedemo",
|
||||||
"lozad",
|
"lozad",
|
||||||
"mastersthesis",
|
"mastersthesis",
|
||||||
"nrwl",
|
"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: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 -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
@ -224,6 +224,10 @@ For help, discussion about best practices, or any other conversation that would
|
|||||||
<tr>
|
<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/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://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>
|
</tr>
|
||||||
</table>
|
</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('/'));
|
beforeEach(() => cy.visit('/'));
|
||||||
|
|
||||||
it('basic load check', () => {
|
it('basic load check', () => {
|
||||||
getTitle().contains('👋 Get Started with AFFINE');
|
getTitle().contains('👋 Get Started with AFFiNE');
|
||||||
|
|
||||||
cy.get('.block_container').contains('The Essentials');
|
cy.get('.block_container').contains('The Essentials');
|
||||||
|
|
||||||
|
@ -12,14 +12,14 @@
|
|||||||
declare namespace Cypress {
|
declare namespace Cypress {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
interface Chainable<Subject> {
|
interface Chainable<Subject> {
|
||||||
login(email: string, password: string): void;
|
// login(email: string, password: string): void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
// -- This is a parent command --
|
// -- This is a parent command --
|
||||||
Cypress.Commands.add('login', (email, password) => {
|
// Cypress.Commands.add('login', (email, password) => {
|
||||||
console.log('Custom command example: Login', email, password);
|
// console.log('Custom command example: Login', email, password);
|
||||||
});
|
// });
|
||||||
//
|
//
|
||||||
// -- This is a child command --
|
// -- This is a child command --
|
||||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
// 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 { LayoutHeader, SettingsSidebar } from '@toeverything/components/layout';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
export function LigoVirgoRootContainer() {
|
export function LigoVirgoRootContainer() {
|
||||||
return (
|
return (
|
||||||
<StyledRootContainer id="idAppRoot">
|
<>
|
||||||
|
<Global
|
||||||
|
styles={css`
|
||||||
|
#root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
/>
|
||||||
<StyledContentContainer>
|
<StyledContentContainer>
|
||||||
<LayoutHeader />
|
<LayoutHeader />
|
||||||
<StyledMainContainer>
|
<StyledMainContainer>
|
||||||
@ -13,7 +22,7 @@ export function LigoVirgoRootContainer() {
|
|||||||
</StyledMainContainer>
|
</StyledMainContainer>
|
||||||
</StyledContentContainer>
|
</StyledContentContainer>
|
||||||
<SettingsSidebar />
|
<SettingsSidebar />
|
||||||
</StyledRootContainer>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,12 +32,6 @@ const StyledMainContainer = styled('div')({
|
|||||||
overflowY: 'hidden',
|
overflowY: 'hidden',
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledRootContainer = styled('div')({
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
height: '100vh',
|
|
||||||
});
|
|
||||||
|
|
||||||
const StyledContentContainer = styled('div')({
|
const StyledContentContainer = styled('div')({
|
||||||
flex: 'auto',
|
flex: 'auto',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -9,11 +9,11 @@ const MemoAffineBoard = memo(AffineBoard, (prev, next) => {
|
|||||||
return prev.rootBlockId === next.rootBlockId;
|
return prev.rootBlockId === next.rootBlockId;
|
||||||
});
|
});
|
||||||
|
|
||||||
type WhiteboardProps = {
|
type EdgelessProps = {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Whiteboard = (props: WhiteboardProps) => {
|
export const Edgeless = (props: EdgelessProps) => {
|
||||||
const { page_id } = useParams();
|
const { page_id } = useParams();
|
||||||
const { user } = useUserAndSpaces();
|
const { user } = useUserAndSpaces();
|
||||||
|
|
@ -22,8 +22,12 @@ export function WorkspaceHome() {
|
|||||||
workspace_id,
|
workspace_id,
|
||||||
user_initial_page_id,
|
user_initial_page_id,
|
||||||
TemplateFactory.generatePageTemplateByGroupKeys({
|
TemplateFactory.generatePageTemplateByGroupKeys({
|
||||||
name: '👋 Get Started with AFFINE',
|
name: '👋 Get Started with AFFiNE',
|
||||||
groupKeys: ['getStartedGroup0', 'getStartedGroup1'],
|
groupKeys: [
|
||||||
|
'getStartedGroup0',
|
||||||
|
'getStartedGroup1',
|
||||||
|
'getStartedGroup2',
|
||||||
|
],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@ import { useUserAndSpaces } from '@toeverything/datasource/state';
|
|||||||
|
|
||||||
import { WorkspaceRootContainer } from './Container';
|
import { WorkspaceRootContainer } from './Container';
|
||||||
import { Page } from './docs';
|
import { Page } from './docs';
|
||||||
|
import { Edgeless } from './Edgeless';
|
||||||
import { WorkspaceHome } from './Home';
|
import { WorkspaceHome } from './Home';
|
||||||
import Labels from './labels';
|
import Labels from './labels';
|
||||||
import Pages from './pages';
|
import Pages from './pages';
|
||||||
import { Whiteboard } from './Whiteboard';
|
|
||||||
|
|
||||||
export function WorkspaceContainer() {
|
export function WorkspaceContainer() {
|
||||||
const { workspace_id } = useParams();
|
const { workspace_id } = useParams();
|
||||||
@ -26,8 +26,8 @@ export function WorkspaceContainer() {
|
|||||||
<Route path="/labels" element={<Labels />} />
|
<Route path="/labels" element={<Labels />} />
|
||||||
<Route path="/pages" element={<Pages />} />
|
<Route path="/pages" element={<Pages />} />
|
||||||
<Route
|
<Route
|
||||||
path="/:page_id/whiteboard"
|
path="/:page_id/edgeless"
|
||||||
element={<Whiteboard workspace={workspace_id} />}
|
element={<Edgeless workspace={workspace_id} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/:page_id"
|
path="/:page_id"
|
||||||
|
@ -73,9 +73,9 @@ module.exports = function (webpackConfig) {
|
|||||||
priority: -5,
|
priority: -5,
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
},
|
},
|
||||||
whiteboard: {
|
edgeless: {
|
||||||
test: /(libs\/components\/board-|[\\/]node_modules[\\/]@tldraw)/,
|
test: /(libs\/components\/board-|[\\/]node_modules[\\/]@tldraw)/,
|
||||||
name: 'whiteboard',
|
name: 'edgeless',
|
||||||
priority: -7,
|
priority: -7,
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
},
|
},
|
||||||
|
@ -715,9 +715,9 @@ export function App() {
|
|||||||
>
|
>
|
||||||
Your data is yours; it is always locally stored and
|
Your data is yours; it is always locally stored and
|
||||||
secured - available to you always. While still being
|
secured - available to you always. While still being
|
||||||
able enjoy collaboration features such as real-time
|
able to enjoy collaboration features such as
|
||||||
editing and sharing with others, without any cloud
|
real-time editing and sharing with others, without
|
||||||
setup.
|
any cloud setup.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</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;
|
scrollBlank?: boolean;
|
||||||
|
|
||||||
isWhiteboard?: boolean;
|
isEdgeless?: boolean;
|
||||||
|
|
||||||
scrollContainer?: HTMLElement;
|
scrollContainer?: HTMLElement;
|
||||||
scrollController?: {
|
scrollController?: {
|
||||||
@ -29,12 +29,12 @@ interface AffineEditorProps {
|
|||||||
function _useConstantWithDispose(
|
function _useConstantWithDispose(
|
||||||
workspace: string,
|
workspace: string,
|
||||||
rootBlockId: string,
|
rootBlockId: string,
|
||||||
isWhiteboard: boolean
|
isEdgeless: boolean
|
||||||
) {
|
) {
|
||||||
const ref = useRef<{ data: BlockEditor; onInit: boolean }>(null);
|
const ref = useRef<{ data: BlockEditor; onInit: boolean }>(null);
|
||||||
const { setCurrentEditors } = useCurrentEditors();
|
const { setCurrentEditors } = useCurrentEditors();
|
||||||
ref.current ??= {
|
ref.current ??= {
|
||||||
data: createEditor(workspace, rootBlockId, isWhiteboard),
|
data: createEditor(workspace, rootBlockId, isEdgeless),
|
||||||
onInit: true,
|
onInit: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,18 +42,14 @@ function _useConstantWithDispose(
|
|||||||
if (ref.current.onInit) {
|
if (ref.current.onInit) {
|
||||||
ref.current.onInit = false;
|
ref.current.onInit = false;
|
||||||
} else {
|
} else {
|
||||||
ref.current.data = createEditor(
|
ref.current.data = createEditor(workspace, rootBlockId, isEdgeless);
|
||||||
workspace,
|
|
||||||
rootBlockId,
|
|
||||||
isWhiteboard
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
setCurrentEditors(prev => ({
|
setCurrentEditors(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[rootBlockId]: ref.current.data,
|
[rootBlockId]: ref.current.data,
|
||||||
}));
|
}));
|
||||||
return () => ref.current.data.dispose();
|
return () => ref.current.data.dispose();
|
||||||
}, [workspace, rootBlockId, isWhiteboard, setCurrentEditors]);
|
}, [workspace, rootBlockId, isEdgeless, setCurrentEditors]);
|
||||||
|
|
||||||
return ref.current.data;
|
return ref.current.data;
|
||||||
}
|
}
|
||||||
@ -64,7 +60,7 @@ export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
|
|||||||
workspace,
|
workspace,
|
||||||
rootBlockId,
|
rootBlockId,
|
||||||
scrollBlank = true,
|
scrollBlank = true,
|
||||||
isWhiteboard,
|
isEdgeless,
|
||||||
scrollController,
|
scrollController,
|
||||||
scrollContainer,
|
scrollContainer,
|
||||||
},
|
},
|
||||||
@ -73,7 +69,7 @@ export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
|
|||||||
const editor = _useConstantWithDispose(
|
const editor = _useConstantWithDispose(
|
||||||
workspace,
|
workspace,
|
||||||
rootBlockId,
|
rootBlockId,
|
||||||
isWhiteboard
|
isEdgeless
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -30,7 +30,7 @@ import { BlockEditor } from '@toeverything/framework/virgo';
|
|||||||
export const createEditor = (
|
export const createEditor = (
|
||||||
workspace: string,
|
workspace: string,
|
||||||
rootBlockId: string,
|
rootBlockId: string,
|
||||||
isWhiteboard?: boolean
|
isEdgeless?: boolean
|
||||||
) => {
|
) => {
|
||||||
const blockEditor = new BlockEditor({
|
const blockEditor = new BlockEditor({
|
||||||
workspace,
|
workspace,
|
||||||
@ -61,7 +61,7 @@ export const createEditor = (
|
|||||||
[Protocol.Block.Type.groupDivider]: new GroupDividerBlock(),
|
[Protocol.Block.Type.groupDivider]: new GroupDividerBlock(),
|
||||||
},
|
},
|
||||||
plugins,
|
plugins,
|
||||||
isWhiteboard,
|
isEdgeless,
|
||||||
});
|
});
|
||||||
|
|
||||||
return blockEditor;
|
return blockEditor;
|
||||||
|
@ -100,6 +100,7 @@ export function groupShapes(
|
|||||||
|
|
||||||
afterShapes[groupId] = TLDR.get_shape_util(TDShapeType.Group).create({
|
afterShapes[groupId] = TLDR.get_shape_util(TDShapeType.Group).create({
|
||||||
id: groupId,
|
id: groupId,
|
||||||
|
affineId: groupId,
|
||||||
childIndex: groupChildIndex,
|
childIndex: groupChildIndex,
|
||||||
parentId: groupParentId,
|
parentId: groupParentId,
|
||||||
point: [groupBounds.minX, groupBounds.minY],
|
point: [groupBounds.minX, groupBounds.minY],
|
||||||
@ -217,7 +218,6 @@ export function groupShapes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: 'group',
|
id: 'group',
|
||||||
before: {
|
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 { TLDR, TldrawApp } from '@toeverything/components/board-state';
|
||||||
import { Divider, Popover, styled } from '@toeverything/components/ui';
|
import { Divider, Popover, styled } from '@toeverything/components/ui';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
|
import { AlignOperation } from './AlignOperation';
|
||||||
import { BorderColorConfig } from './BorderColorConfig';
|
import { BorderColorConfig } from './BorderColorConfig';
|
||||||
import { DeleteShapes } from './DeleteOperation';
|
import { DeleteShapes } from './DeleteOperation';
|
||||||
import { FillColorConfig } from './FillColorConfig';
|
import { FillColorConfig } from './FillColorConfig';
|
||||||
@ -8,6 +9,7 @@ import { FontSizeConfig } from './FontSizeConfig';
|
|||||||
import { FrameFillColorConfig } from './FrameFillColorConfig';
|
import { FrameFillColorConfig } from './FrameFillColorConfig';
|
||||||
import { Group, UnGroup } from './GroupOperation';
|
import { Group, UnGroup } from './GroupOperation';
|
||||||
import { Lock, Unlock } from './LockOperation';
|
import { Lock, Unlock } from './LockOperation';
|
||||||
|
import { MoveCoverageConfig } from './MoveCoverage';
|
||||||
import { StrokeLineStyleConfig } from './stroke-line-style-config';
|
import { StrokeLineStyleConfig } from './stroke-line-style-config';
|
||||||
import { getAnchor, useConfig } from './utils';
|
import { getAnchor, useConfig } from './utils';
|
||||||
|
|
||||||
@ -91,6 +93,19 @@ export const CommandPanel = ({ app }: { app: TldrawApp }) => {
|
|||||||
shapes={config.deleteShapes.selectedShapes}
|
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);
|
const nodes = Object.entries(configNodes).filter(([key, node]) => !!node);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import type { TldrawApp } from '@toeverything/components/board-state';
|
import { TLDR, TldrawApp } from '@toeverything/components/board-state';
|
||||||
import type { TDShape } from '@toeverything/components/board-types';
|
import { TDShape, TDShapeType } from '@toeverything/components/board-types';
|
||||||
import { GroupIcon, UngroupIcon } from '@toeverything/components/icons';
|
import { GroupIcon, UngroupIcon } from '@toeverything/components/icons';
|
||||||
import { IconButton, Tooltip } from '@toeverything/components/ui';
|
import { IconButton, Tooltip } from '@toeverything/components/ui';
|
||||||
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
import { getShapeIds } from './utils';
|
import { getShapeIds } from './utils';
|
||||||
|
|
||||||
interface GroupAndUnGroupProps {
|
interface GroupAndUnGroupProps {
|
||||||
@ -10,8 +11,38 @@ interface GroupAndUnGroupProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Group = ({ app, shapes }: GroupAndUnGroupProps) => {
|
export const Group = ({ app, shapes }: GroupAndUnGroupProps) => {
|
||||||
const group = () => {
|
const group = async () => {
|
||||||
app.group(getShapeIds(shapes));
|
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 (
|
return (
|
||||||
<Tooltip content="Group">
|
<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: 'frame', label: 'Frame', tooltip: 'Frame', icon: FrameIcon },
|
||||||
{
|
{
|
||||||
type: TDShapeType.Editor,
|
type: TDShapeType.Editor,
|
||||||
label: 'Text',
|
label: 'Text Block',
|
||||||
tooltip: 'Text',
|
tooltip: 'Text Block',
|
||||||
icon: TextIcon,
|
icon: TextIcon,
|
||||||
},
|
},
|
||||||
{ type: 'shapes', component: ShapeTools },
|
{ type: 'shapes', component: ShapeTools },
|
||||||
@ -61,9 +61,9 @@ const tools: Array<{
|
|||||||
{ type: 'Connector', component: LineTools },
|
{ type: 'Connector', component: LineTools },
|
||||||
// { type: 'erase', label: 'Erase', tooltip: 'Erase', icon: EraseIcon },
|
// { type: 'erase', label: 'Erase', tooltip: 'Erase', icon: EraseIcon },
|
||||||
{
|
{
|
||||||
type: TDShapeType.HandDraw,
|
type: TDShapeType.HandDrag,
|
||||||
label: 'HandDraw',
|
label: 'Hand Drag',
|
||||||
tooltip: 'HandDraw',
|
tooltip: 'Hand Drag',
|
||||||
icon: HandToolIcon,
|
icon: HandToolIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -111,7 +111,8 @@ export class TranslateSession extends BaseSession {
|
|||||||
Utils.boundsContain(
|
Utils.boundsContain(
|
||||||
TLDR.get_bounds(shap),
|
TLDR.get_bounds(shap),
|
||||||
TLDR.get_bounds(shapItem)
|
TLDR.get_bounds(shapItem)
|
||||||
)
|
) &&
|
||||||
|
!shapItem.isLocked
|
||||||
) {
|
) {
|
||||||
selectedShapes.push(shapItem);
|
selectedShapes.push(shapItem);
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from '@toeverything/components/board-types';
|
} from '@toeverything/components/board-types';
|
||||||
import { MIN_PAGE_WIDTH } from '@toeverything/components/editor-core';
|
import { MIN_PAGE_WIDTH } from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
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 { memo, useCallback, useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
defaultTextStyle,
|
defaultTextStyle,
|
||||||
@ -135,6 +135,15 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
|||||||
}
|
}
|
||||||
}, [app, state, shape.id, editingText, editingId]);
|
}, [app, state, shape.id, editingText, editingId]);
|
||||||
|
|
||||||
|
const onMouseDown = useCallback(
|
||||||
|
(e: MouseEvent) => {
|
||||||
|
if (e.detail === 2) {
|
||||||
|
app.setEditingText(shape.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[app, shape.id]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HTMLContainer ref={ref} {...events}>
|
<HTMLContainer ref={ref} {...events}>
|
||||||
<Container
|
<Container
|
||||||
@ -143,12 +152,13 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
|||||||
onPointerDown={stopPropagation}
|
onPointerDown={stopPropagation}
|
||||||
onMouseEnter={activateIfEditing}
|
onMouseEnter={activateIfEditing}
|
||||||
onDragEnter={activateIfEditing}
|
onDragEnter={activateIfEditing}
|
||||||
|
onMouseDown={onMouseDown}
|
||||||
>
|
>
|
||||||
<MemoAffineEditor
|
<MemoAffineEditor
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
rootBlockId={rootBlockId}
|
rootBlockId={rootBlockId}
|
||||||
scrollBlank={false}
|
scrollBlank={false}
|
||||||
isWhiteboard
|
isEdgeless
|
||||||
/>
|
/>
|
||||||
{editingText ? null : <Mask />}
|
{editingText ? null : <Mask />}
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -3853,12 +3853,9 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private get_viewbox_from_svg = (svgStr: string | ArrayBuffer | null) => {
|
private get_viewbox_from_svg = (svgStr: string | ArrayBuffer | null) => {
|
||||||
const viewBoxRegex =
|
|
||||||
/.*?viewBox=["'](-?[\d.]+[, ]+-?[\d.]+[, ][\d.]+[, ][\d.]+)["']/;
|
|
||||||
|
|
||||||
if (typeof svgStr === 'string') {
|
if (typeof svgStr === 'string') {
|
||||||
const matches = svgStr.match(viewBoxRegex);
|
let viewBox = new DOMParser().parseFromString(svgStr, 'text/xml');
|
||||||
return matches && matches.length >= 2 ? matches[1] : null;
|
return viewBox.children[0].getAttribute('viewBox');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn('could not get viewbox from svg string');
|
console.warn('could not get viewbox from svg string');
|
||||||
@ -3994,7 +3991,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
this.patchState({
|
this.patchState({
|
||||||
settings: {
|
settings: {
|
||||||
forcePanning:
|
forcePanning:
|
||||||
this.currentTool.type === TDShapeType.HandDraw,
|
this.currentTool.type === TDShapeType.HandDrag,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.spaceKey = false;
|
this.spaceKey = false;
|
||||||
|
@ -9,8 +9,8 @@ enum Status {
|
|||||||
Draw = 'draw',
|
Draw = 'draw',
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HandDrawTool extends BaseTool {
|
export class HandDragTool extends BaseTool {
|
||||||
override type = TDShapeType.HandDraw as const;
|
override type = TDShapeType.HandDrag as const;
|
||||||
|
|
||||||
override status: Status = Status.Idle;
|
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 { EllipseTool } from './ellipse-tool';
|
||||||
import { EraseTool } from './erase-tool';
|
import { EraseTool } from './erase-tool';
|
||||||
import { FrameTool } from './frame-tool/frame-tool';
|
import { FrameTool } from './frame-tool/frame-tool';
|
||||||
import { HandDrawTool } from './hand-draw';
|
import { HandDragTool } from './hand-drag';
|
||||||
import { HexagonTool } from './hexagon-tool';
|
import { HexagonTool } from './hexagon-tool';
|
||||||
import { HighlightTool } from './highlight-tool';
|
import { HighlightTool } from './highlight-tool';
|
||||||
import { LaserTool } from './laser-tool';
|
import { LaserTool } from './laser-tool';
|
||||||
@ -32,7 +32,7 @@ export interface ToolsMap {
|
|||||||
[TDShapeType.Highlight]: typeof HighlightTool;
|
[TDShapeType.Highlight]: typeof HighlightTool;
|
||||||
[TDShapeType.Editor]: typeof EditorTool;
|
[TDShapeType.Editor]: typeof EditorTool;
|
||||||
[TDShapeType.WhiteArrow]: typeof WhiteArrowTool;
|
[TDShapeType.WhiteArrow]: typeof WhiteArrowTool;
|
||||||
[TDShapeType.HandDraw]: typeof HandDrawTool;
|
[TDShapeType.HandDrag]: typeof HandDragTool;
|
||||||
[TDShapeType.Laser]: typeof LaserTool;
|
[TDShapeType.Laser]: typeof LaserTool;
|
||||||
[TDShapeType.Frame]: typeof FrameTool;
|
[TDShapeType.Frame]: typeof FrameTool;
|
||||||
}
|
}
|
||||||
@ -59,6 +59,6 @@ export const tools: { [K in TDToolType]: ToolsMap[K] } = {
|
|||||||
[TDShapeType.Hexagon]: HexagonTool,
|
[TDShapeType.Hexagon]: HexagonTool,
|
||||||
[TDShapeType.WhiteArrow]: WhiteArrowTool,
|
[TDShapeType.WhiteArrow]: WhiteArrowTool,
|
||||||
[TDShapeType.Laser]: LaserTool,
|
[TDShapeType.Laser]: LaserTool,
|
||||||
[TDShapeType.HandDraw]: HandDrawTool,
|
[TDShapeType.HandDrag]: HandDragTool,
|
||||||
[TDShapeType.Frame]: FrameTool,
|
[TDShapeType.Frame]: FrameTool,
|
||||||
};
|
};
|
||||||
|
@ -212,7 +212,7 @@ export type TDToolType =
|
|||||||
| TDShapeType.WhiteArrow
|
| TDShapeType.WhiteArrow
|
||||||
| TDShapeType.Editor
|
| TDShapeType.Editor
|
||||||
| TDShapeType.Frame
|
| TDShapeType.Frame
|
||||||
| TDShapeType.HandDraw;
|
| TDShapeType.HandDrag;
|
||||||
|
|
||||||
export type Easing =
|
export type Easing =
|
||||||
| 'linear'
|
| 'linear'
|
||||||
@ -287,7 +287,7 @@ export enum TDShapeType {
|
|||||||
Video = 'video',
|
Video = 'video',
|
||||||
Editor = 'editor',
|
Editor = 'editor',
|
||||||
WhiteArrow = 'white-arrow',
|
WhiteArrow = 'white-arrow',
|
||||||
HandDraw = 'hand-draw',
|
HandDrag = 'hand-drag',
|
||||||
Frame = 'frame',
|
Frame = 'frame',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,28 +2,28 @@
|
|||||||
import {
|
import {
|
||||||
Descendant,
|
Descendant,
|
||||||
Editor,
|
Editor,
|
||||||
|
Location,
|
||||||
|
Node as SlateNode,
|
||||||
|
Path,
|
||||||
Point,
|
Point,
|
||||||
Transforms,
|
|
||||||
Range,
|
Range,
|
||||||
Text,
|
Text,
|
||||||
Location,
|
Transforms,
|
||||||
Path,
|
|
||||||
Node as SlateNode,
|
|
||||||
} from 'slate';
|
} from 'slate';
|
||||||
import { ReactEditor } from 'slate-react';
|
import { ReactEditor } from 'slate-react';
|
||||||
|
|
||||||
import {
|
|
||||||
getCommentsIdsOnTextNode,
|
|
||||||
getEditorMarkForCommentId,
|
|
||||||
MARKDOWN_STYLE_MAP,
|
|
||||||
MatchRes,
|
|
||||||
} from './utils';
|
|
||||||
import {
|
import {
|
||||||
fontBgColorPalette,
|
fontBgColorPalette,
|
||||||
fontColorPalette,
|
fontColorPalette,
|
||||||
type TextAlignOptions,
|
type TextAlignOptions,
|
||||||
type TextStyleMark,
|
type TextStyleMark,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import {
|
||||||
|
getCommentsIdsOnTextNode,
|
||||||
|
getEditorMarkForCommentId,
|
||||||
|
MARKDOWN_STYLE_MAP,
|
||||||
|
MatchRes,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
function isInlineAndVoid(editor: Editor, el: any) {
|
function isInlineAndVoid(editor: Editor, el: any) {
|
||||||
return editor.isInline(el) && editor.isVoid(el);
|
return editor.isInline(el) && editor.isVoid(el);
|
||||||
@ -567,8 +567,51 @@ class SlateUtils {
|
|||||||
anchor: point1,
|
anchor: point1,
|
||||||
focus: point2,
|
focus: point2,
|
||||||
});
|
});
|
||||||
// @ts-ignore
|
if (!fragment.length) {
|
||||||
return fragment[0].children;
|
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() {
|
public getSplitContentsBySelection() {
|
||||||
|
@ -173,7 +173,7 @@ export const BulletView = ({ block, editor }: CreateView) => {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return tabBlock(block, isShiftKey);
|
return tabBlock(editor, block, isShiftKey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import {
|
import {
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
BaseView,
|
BaseView,
|
||||||
SelectBlock,
|
SelectBlock,
|
||||||
} from '@toeverything/framework/virgo';
|
} from '@toeverything/framework/virgo';
|
||||||
import { Protocol, services } from '@toeverything/datasource/db-service';
|
|
||||||
import { FigmaView } from './FigmaView';
|
import { FigmaView } from './FigmaView';
|
||||||
|
|
||||||
export class FigmaBlock extends BaseView {
|
export class FigmaBlock extends BaseView {
|
||||||
@ -19,7 +19,10 @@ export class FigmaBlock extends BaseView {
|
|||||||
const tag_name = el.tagName;
|
const tag_name = el.tagName;
|
||||||
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
|
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
|
||||||
const href = el.getAttribute('href');
|
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 [
|
return [
|
||||||
{
|
{
|
||||||
type: this.type,
|
type: this.type,
|
||||||
|
@ -103,7 +103,7 @@ export const GroupView = (props: CreateView) => {
|
|||||||
<View {...props} />
|
<View {...props} />
|
||||||
</GroupContainer>
|
</GroupContainer>
|
||||||
|
|
||||||
{editor.isWhiteboard ? null : (
|
{editor.isEdgeless ? null : (
|
||||||
<GroupAction onAddGroup={addGroup} />
|
<GroupAction onAddGroup={addGroup} />
|
||||||
)}
|
)}
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
@ -49,7 +49,7 @@ const weakSqlCreator = (weak_sql_express = ''): Promise<Constraint[]> => {
|
|||||||
constraints.push({
|
constraints.push({
|
||||||
field: field.trim(),
|
field: field.trim(),
|
||||||
relation: relation.trim() as Relation,
|
relation: relation.trim() as Relation,
|
||||||
value: pickValue(value.replace(/&&|&|;/, '').trim()),
|
value: pickValue(value.replace(/&&|&|;/g, '').trim()),
|
||||||
});
|
});
|
||||||
|
|
||||||
/* meaningless return value */
|
/* meaningless return value */
|
||||||
|
@ -167,7 +167,7 @@ export const NumberedView = ({ block, editor }: CreateView) => {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
tabBlock(block, isShiftKey);
|
tabBlock(editor, block, isShiftKey);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2,13 +2,11 @@ import { useState } from 'react';
|
|||||||
|
|
||||||
import { CustomText, TextProps } from '@toeverything/components/common';
|
import { CustomText, TextProps } from '@toeverything/components/common';
|
||||||
import {
|
import {
|
||||||
mergeGroup,
|
BlockPendantProvider,
|
||||||
RenderBlockChildren,
|
RenderBlockChildren,
|
||||||
splitGroup,
|
splitGroup,
|
||||||
supportChildren,
|
supportChildren,
|
||||||
unwrapGroup,
|
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
BlockPendantProvider,
|
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
@ -16,7 +14,7 @@ import { CreateView } from '@toeverything/framework/virgo';
|
|||||||
import { BlockContainer } from '../../components/BlockContainer';
|
import { BlockContainer } from '../../components/BlockContainer';
|
||||||
import { IndentWrapper } from '../../components/IndentWrapper';
|
import { IndentWrapper } from '../../components/IndentWrapper';
|
||||||
import { TextManage } from '../../components/text-manage';
|
import { TextManage } from '../../components/text-manage';
|
||||||
import { tabBlock } from '../../utils/indent';
|
import { dedentBlock, tabBlock } from '../../utils/indent';
|
||||||
interface CreateTextView extends CreateView {
|
interface CreateTextView extends CreateView {
|
||||||
// TODO: need to optimize
|
// TODO: need to optimize
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
@ -69,24 +67,36 @@ export const TextView = ({
|
|||||||
const { contentBeforeSelection, contentAfterSelection } = splitContents;
|
const { contentBeforeSelection, contentAfterSelection } = splitContents;
|
||||||
const before = [...contentBeforeSelection.content];
|
const before = [...contentBeforeSelection.content];
|
||||||
const after = [...contentAfterSelection.content];
|
const after = [...contentAfterSelection.content];
|
||||||
const _nextBlockChildren = await block.children();
|
const nextBlockChildren = await block.children();
|
||||||
const _nextBlock = await editor.createBlock('text');
|
const nextBlock = await editor.createBlock('text');
|
||||||
await _nextBlock.setProperty('text', {
|
if (!nextBlock) {
|
||||||
|
throw new Error('Failed to create text block');
|
||||||
|
}
|
||||||
|
await nextBlock.setProperty('text', {
|
||||||
value: after as CustomText[],
|
value: after as CustomText[],
|
||||||
});
|
});
|
||||||
_nextBlock.append(..._nextBlockChildren);
|
|
||||||
block.removeChildren();
|
|
||||||
await block.setProperty('text', {
|
await block.setProperty('text', {
|
||||||
value: before as CustomText[],
|
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;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBackspace: TextProps['handleBackSpace'] = async props => {
|
const onBackspace: TextProps['handleBackSpace'] = editor.withBatch(
|
||||||
return await editor.withSuspend(async () => {
|
async props => {
|
||||||
const { isCollAndStart } = props;
|
const { isCollAndStart } = props;
|
||||||
if (!isCollAndStart) {
|
if (!isCollAndStart) {
|
||||||
return false;
|
return false;
|
||||||
@ -95,28 +105,44 @@ export const TextView = ({
|
|||||||
await block.setType('text');
|
await block.setType('text');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (editor.getRootBlockId() === block.id) {
|
||||||
|
// Can not delete
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const parentBlock = await block.parent();
|
const parentBlock = await block.parent();
|
||||||
|
|
||||||
if (!parentBlock) {
|
if (!parentBlock) {
|
||||||
return false;
|
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 children = await block.children();
|
||||||
const preNode = await block.physicallyPerviousSibling();
|
const preNode = await block.physicallyPerviousSibling();
|
||||||
// FIXME support children do not means has textBlock
|
// FIXME support children do not means has textBlock
|
||||||
// TODO: abstract this part of code
|
// TODO: abstract this part of code
|
||||||
if (preNode) {
|
if (preNode) {
|
||||||
if (supportChildren(preNode)) {
|
if (supportChildren(preNode)) {
|
||||||
editor.suspend(true);
|
|
||||||
await editor.selectionManager.activePreviousNode(
|
await editor.selectionManager.activePreviousNode(
|
||||||
block.id,
|
block.id,
|
||||||
'end'
|
'end'
|
||||||
);
|
);
|
||||||
if (
|
if (!block.blockProvider?.isEmpty()) {
|
||||||
block.getProperty('text').value[0] &&
|
|
||||||
block.getProperty('text').value[0]?.text !== ''
|
|
||||||
) {
|
|
||||||
const value = [
|
const value = [
|
||||||
...preNode.getProperty('text').value,
|
...preNode.getProperty('text').value,
|
||||||
...block.getProperty('text').value,
|
...block.getProperty('text').value,
|
||||||
@ -127,14 +153,14 @@ export const TextView = ({
|
|||||||
}
|
}
|
||||||
await preNode.append(...children);
|
await preNode.append(...children);
|
||||||
await block.remove();
|
await block.remove();
|
||||||
editor.suspend(false);
|
|
||||||
} else {
|
} else {
|
||||||
|
// If not pre node, block should be merged to the parent block
|
||||||
// TODO: point does not clear
|
// TODO: point does not clear
|
||||||
await editor.selectionManager.activePreviousNode(
|
await editor.selectionManager.activePreviousNode(
|
||||||
block.id,
|
block.id,
|
||||||
'start'
|
'start'
|
||||||
);
|
);
|
||||||
if (block.blockProvider.isEmpty()) {
|
if (block.blockProvider?.isEmpty()) {
|
||||||
await block.remove();
|
await block.remove();
|
||||||
const parentChild = await parentBlock.children();
|
const parentChild = await parentBlock.children();
|
||||||
if (
|
if (
|
||||||
@ -142,6 +168,8 @@ export const TextView = ({
|
|||||||
Protocol.Block.Type.group &&
|
Protocol.Block.Type.group &&
|
||||||
!parentChild.length
|
!parentChild.length
|
||||||
) {
|
) {
|
||||||
|
const preParent =
|
||||||
|
await parentBlock.previousSibling();
|
||||||
await editor.selectionManager.setSelectedNodesIds(
|
await editor.selectionManager.setSelectedNodesIds(
|
||||||
[preParent?.id ?? editor.getRootBlockId()]
|
[preParent?.id ?? editor.getRootBlockId()]
|
||||||
);
|
);
|
||||||
@ -182,20 +210,13 @@ export const TextView = ({
|
|||||||
await parentBlock.remove();
|
await parentBlock.remove();
|
||||||
}
|
}
|
||||||
return true;
|
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;
|
return true;
|
||||||
});
|
}
|
||||||
};
|
);
|
||||||
|
|
||||||
const handleConvert = async (
|
const handleConvert = async (
|
||||||
toType: string,
|
toType: string,
|
||||||
options?: Record<string, unknown>
|
options?: Record<string, unknown>
|
||||||
@ -211,7 +232,7 @@ export const TextView = ({
|
|||||||
block.firstCreateFlag = true;
|
block.firstCreateFlag = true;
|
||||||
};
|
};
|
||||||
const onTab: TextProps['handleTab'] = async ({ isShiftKey }) => {
|
const onTab: TextProps['handleTab'] = async ({ isShiftKey }) => {
|
||||||
await tabBlock(block, isShiftKey);
|
await tabBlock(editor, block, isShiftKey);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ export const TodoView = ({ block, editor }: CreateView) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const on_tab: TextProps['handleTab'] = async ({ isShiftKey }) => {
|
const on_tab: TextProps['handleTab'] = async ({ isShiftKey }) => {
|
||||||
await tabBlock(block, isShiftKey);
|
await tabBlock(editor, block, isShiftKey);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import {
|
import {
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
BaseView,
|
BaseView,
|
||||||
SelectBlock,
|
SelectBlock,
|
||||||
} from '@toeverything/framework/virgo';
|
} from '@toeverything/framework/virgo';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
|
||||||
import { YoutubeView } from './YoutubeView';
|
import { YoutubeView } from './YoutubeView';
|
||||||
|
|
||||||
export class YoutubeBlock extends BaseView {
|
export class YoutubeBlock extends BaseView {
|
||||||
@ -19,7 +19,10 @@ export class YoutubeBlock extends BaseView {
|
|||||||
const tag_name = el.tagName;
|
const tag_name = el.tagName;
|
||||||
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
|
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
|
||||||
const href = el.getAttribute('href');
|
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 [
|
return [
|
||||||
{
|
{
|
||||||
type: this.type,
|
type: this.type,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const _regex =
|
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) => {
|
export const isAffineUrl = (url?: string) => {
|
||||||
if (!url) return false;
|
if (!url) return false;
|
||||||
@ -7,5 +7,5 @@ export const isAffineUrl = (url?: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const toAffineEmbedUrl = (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 => {
|
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=)([^#&?]*).*/;
|
const _regexp = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=)([^#&?]*).*/;
|
||||||
|
@ -4,10 +4,7 @@ import {
|
|||||||
type SlateUtils,
|
type SlateUtils,
|
||||||
type TextProps,
|
type TextProps,
|
||||||
} from '@toeverything/components/common';
|
} from '@toeverything/components/common';
|
||||||
import {
|
import { useOnSelectActive } from '@toeverything/components/editor-core';
|
||||||
useOnSelectActive,
|
|
||||||
useOnSelectSetSelection,
|
|
||||||
} from '@toeverything/components/editor-core';
|
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import { ContentColumnValue } from '@toeverything/datasource/db-service';
|
import { ContentColumnValue } from '@toeverything/datasource/db-service';
|
||||||
import {
|
import {
|
||||||
@ -119,15 +116,6 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
|
|
||||||
const properties = block.getProperties();
|
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(
|
const onTextViewActive = useCallback(
|
||||||
(point: CursorTypes) => {
|
(point: CursorTypes) => {
|
||||||
// TODO code to be optimized
|
// TODO code to be optimized
|
||||||
@ -209,7 +197,6 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
useOnSelectActive(block.id, onTextViewActive);
|
useOnSelectActive(block.id, onTextViewActive);
|
||||||
useOnSelectSetSelection<'Range'>(block.id, onTextViewSetSelection);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (textRef.current) {
|
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 { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import { AsyncBlock } from '@toeverything/framework/virgo';
|
import { AsyncBlock } from '@toeverything/framework/virgo';
|
||||||
import type { TodoAsyncBlock } from '../blocks/todo/types';
|
import type { TodoAsyncBlock } from '../blocks/todo/types';
|
||||||
@ -6,14 +9,19 @@ import type { TodoAsyncBlock } from '../blocks/todo/types';
|
|||||||
/**
|
/**
|
||||||
* Is the block in top level
|
* Is the block in top level
|
||||||
*/
|
*/
|
||||||
export const isTopLevelBlock = (parentBlock: AsyncBlock): boolean => {
|
export const isTopLevelBlock = (
|
||||||
|
editor: BlockEditor,
|
||||||
|
block: AsyncBlock
|
||||||
|
): boolean => {
|
||||||
return (
|
return (
|
||||||
parentBlock.type === Protocol.Block.Type.group ||
|
editor.getRootBlockId() === block.id ||
|
||||||
parentBlock.type === Protocol.Block.Type.page
|
block.type === Protocol.Block.Type.group ||
|
||||||
|
block.type === Protocol.Block.Type.page
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Move down
|
||||||
* @returns true if indent is success
|
* @returns true if indent is success
|
||||||
* @example
|
* @example
|
||||||
* ```
|
* ```
|
||||||
@ -31,7 +39,6 @@ export const isTopLevelBlock = (parentBlock: AsyncBlock): boolean => {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
const indentBlock = async (block: TodoAsyncBlock) => {
|
const indentBlock = async (block: TodoAsyncBlock) => {
|
||||||
// Move down
|
|
||||||
const previousBlock = await block.previousSibling();
|
const previousBlock = await block.previousSibling();
|
||||||
|
|
||||||
if (!previousBlock || !supportChildren(previousBlock)) {
|
if (!previousBlock || !supportChildren(previousBlock)) {
|
||||||
@ -57,6 +64,7 @@ const indentBlock = async (block: TodoAsyncBlock) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Move up
|
||||||
* @returns true if dedent is success
|
* @returns true if dedent is success
|
||||||
* @example
|
* @example
|
||||||
* ```
|
* ```
|
||||||
@ -73,13 +81,15 @@ const indentBlock = async (block: TodoAsyncBlock) => {
|
|||||||
* └─ [ ]
|
* └─ [ ]
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
const dedentBlock = async (block: AsyncBlock) => {
|
export const dedentBlock = async (editor: BlockEditor, block: AsyncBlock) => {
|
||||||
// Move up
|
if (editor.getRootBlockId() === block.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
let parentBlock = await block.parent();
|
let parentBlock = await block.parent();
|
||||||
if (!parentBlock) {
|
if (!parentBlock) {
|
||||||
throw new Error('Failed to dedent block! Parent block not found!');
|
throw new Error('Failed to dedent block! Parent block not found!');
|
||||||
}
|
}
|
||||||
if (isTopLevelBlock(parentBlock)) {
|
if (isTopLevelBlock(editor, parentBlock)) {
|
||||||
// Top, do nothing
|
// Top, do nothing
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -111,9 +121,13 @@ const dedentBlock = async (block: AsyncBlock) => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tabBlock = async (block: AsyncBlock, isShiftKey: boolean) => {
|
export const tabBlock = async (
|
||||||
|
editor: BlockEditor,
|
||||||
|
block: AsyncBlock,
|
||||||
|
isShiftKey: boolean
|
||||||
|
) => {
|
||||||
if (isShiftKey) {
|
if (isShiftKey) {
|
||||||
return await dedentBlock(block);
|
return await dedentBlock(editor, block);
|
||||||
} else {
|
} else {
|
||||||
return await indentBlock(block);
|
return await indentBlock(block);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import type { BlockEditor } from './editor';
|
|
||||||
import { styled, usePatchNodes } from '@toeverything/components/ui';
|
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 {
|
import {
|
||||||
Protocol,
|
Protocol,
|
||||||
services,
|
services,
|
||||||
type ReturnUnobserve,
|
type ReturnUnobserve,
|
||||||
} from '@toeverything/datasource/db-service';
|
} 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 { useIsOnDrag } from './hooks';
|
||||||
|
import { addNewGroup, appendNewGroup } from './recast-block';
|
||||||
|
import { SelectionRect, SelectionRef } from './Selection';
|
||||||
|
|
||||||
interface RenderRootProps {
|
interface RenderRootProps {
|
||||||
editor: BlockEditor;
|
editor: BlockEditor;
|
||||||
@ -160,7 +160,7 @@ export const RenderRoot = ({
|
|||||||
return (
|
return (
|
||||||
<EditorProvider value={{ editor, editorElement }}>
|
<EditorProvider value={{ editor, editorElement }}>
|
||||||
<Container
|
<Container
|
||||||
isWhiteboard={editor.isWhiteboard}
|
isEdgeless={editor.isEdgeless}
|
||||||
ref={ref => {
|
ref={ref => {
|
||||||
if (ref != null && ref !== editor.container) {
|
if (ref != null && ref !== editor.container) {
|
||||||
editor.container = ref;
|
editor.container = ref;
|
||||||
@ -188,7 +188,7 @@ export const RenderRoot = ({
|
|||||||
</Content>
|
</Content>
|
||||||
{/** TODO: remove selectionManager insert */}
|
{/** TODO: remove selectionManager insert */}
|
||||||
{editor && <SelectionRect ref={selectionRef} editor={editor} />}
|
{editor && <SelectionRect ref={selectionRef} editor={editor} />}
|
||||||
{editor.isWhiteboard ? null : <ScrollBlank editor={editor} />}
|
{editor.isEdgeless ? null : <ScrollBlank editor={editor} />}
|
||||||
{patchedNodes}
|
{patchedNodes}
|
||||||
</Container>
|
</Container>
|
||||||
</EditorProvider>
|
</EditorProvider>
|
||||||
@ -262,16 +262,10 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
|
|||||||
const PADDING_X = 150;
|
const PADDING_X = 150;
|
||||||
|
|
||||||
const Container = styled('div')(
|
const Container = styled('div')(
|
||||||
({
|
({ isEdgeless, isOnDrag }: { isEdgeless: boolean; isOnDrag: boolean }) => ({
|
||||||
isWhiteboard,
|
|
||||||
isOnDrag,
|
|
||||||
}: {
|
|
||||||
isWhiteboard: boolean;
|
|
||||||
isOnDrag: boolean;
|
|
||||||
}) => ({
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: isWhiteboard ? 0 : `72px ${PADDING_X}px 0 ${PADDING_X}px`,
|
padding: isEdgeless ? 0 : `72px ${PADDING_X}px 0 ${PADDING_X}px`,
|
||||||
minWidth: isWhiteboard ? 'unset' : '940px',
|
minWidth: isEdgeless ? 'unset' : '940px',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
...(isOnDrag && {
|
...(isOnDrag && {
|
||||||
cursor: 'grabbing',
|
cursor: 'grabbing',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import { domToRect, Point } from '@toeverything/utils';
|
import { domToRect, Point } from '@toeverything/utils';
|
||||||
import { AsyncBlock } from '../..';
|
import { AsyncBlock } from '../..';
|
||||||
import { GridDropType } from '../commands/types';
|
import { GridDropType } from '../commands/types';
|
||||||
@ -6,7 +7,6 @@ import { Editor } from '../editor';
|
|||||||
import { BlockDropPlacement, GroupDirection } from '../types';
|
import { BlockDropPlacement, GroupDirection } from '../types';
|
||||||
// TODO: Evaluate implementing custom events with Rxjs
|
// TODO: Evaluate implementing custom events with Rxjs
|
||||||
import EventEmitter from 'eventemitter3';
|
import EventEmitter from 'eventemitter3';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
|
||||||
|
|
||||||
enum DragType {
|
enum DragType {
|
||||||
dragBlock = 'dragBlock',
|
dragBlock = 'dragBlock',
|
||||||
@ -281,6 +281,10 @@ export class DragDropManager {
|
|||||||
this._editor.getRootBlockId()
|
this._editor.getRootBlockId()
|
||||||
);
|
);
|
||||||
let direction = BlockDropPlacement.none;
|
let direction = BlockDropPlacement.none;
|
||||||
|
if (!rootBlock || !rootBlock.dom) {
|
||||||
|
console.warn('Can not find dom bind with block', rootBlock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const rootBlockRect = domToRect(rootBlock.dom);
|
const rootBlockRect = domToRect(rootBlock.dom);
|
||||||
let targetBlock: AsyncBlock | undefined;
|
let targetBlock: AsyncBlock | undefined;
|
||||||
let typesInfo = {
|
let typesInfo = {
|
||||||
@ -303,6 +307,10 @@ export class DragDropManager {
|
|||||||
if (direction !== BlockDropPlacement.none) {
|
if (direction !== BlockDropPlacement.none) {
|
||||||
const blockList = await this._editor.getBlockListByLevelOrder();
|
const blockList = await this._editor.getBlockListByLevelOrder();
|
||||||
targetBlock = blockList.find(block => {
|
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 domRect = domToRect(block.dom);
|
||||||
const pointChecker =
|
const pointChecker =
|
||||||
direction === BlockDropPlacement.outerLeft
|
direction === BlockDropPlacement.outerLeft
|
||||||
|
@ -1,55 +1,51 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
import HotKeys from 'hotkeys-js';
|
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 {
|
import type {
|
||||||
BlockFlavors,
|
BlockFlavors,
|
||||||
ReturnEditorBlock,
|
ReturnEditorBlock,
|
||||||
UpdateEditorBlock,
|
UpdateEditorBlock,
|
||||||
} from '@toeverything/datasource/db-service';
|
} 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 { 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 { BrowserClipboard } from './clipboard/browser-clipboard';
|
||||||
import { ClipboardPopulator } from './clipboard/clipboard-populator';
|
import { ClipboardPopulator } from './clipboard/clipboard-populator';
|
||||||
import { BlockHelper } from './block/block-helper';
|
import { EditorCommands } from './commands';
|
||||||
import { DragDropManager } from './drag-drop';
|
|
||||||
import { EditorConfig } from './config';
|
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 {
|
export interface EditorCtorProps {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
views: Partial<Record<keyof BlockFlavors, BaseView>>;
|
views: Partial<Record<keyof BlockFlavors, BaseView>>;
|
||||||
plugins: PluginCreator[];
|
plugins: PluginCreator[];
|
||||||
rootBlockId: string;
|
rootBlockId: string;
|
||||||
isWhiteboard?: boolean;
|
isEdgeless?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Editor implements Virgo {
|
export class Editor implements Virgo {
|
||||||
private _cacheManager = new LRUCache<string, Promise<AsyncBlock | null>>({
|
private _cacheManager = new Map<string, Promise<AsyncBlock | null>>();
|
||||||
max: 8192,
|
|
||||||
ttl: 1000 * 60 * 30,
|
|
||||||
});
|
|
||||||
public mouseManager = new MouseManager(this);
|
public mouseManager = new MouseManager(this);
|
||||||
public selectionManager = new SelectionManager(this);
|
public selectionManager = new SelectionManager(this);
|
||||||
public keyboardManager = new KeyboardManager(this);
|
public keyboardManager = new KeyboardManager(this);
|
||||||
@ -75,7 +71,7 @@ export class Editor implements Virgo {
|
|||||||
render: PatchNode;
|
render: PatchNode;
|
||||||
has: (key: string) => boolean;
|
has: (key: string) => boolean;
|
||||||
};
|
};
|
||||||
public isWhiteboard = false;
|
public isEdgeless = false;
|
||||||
private _isDisposed = false;
|
private _isDisposed = false;
|
||||||
|
|
||||||
constructor(props: EditorCtorProps) {
|
constructor(props: EditorCtorProps) {
|
||||||
@ -85,8 +81,8 @@ export class Editor implements Virgo {
|
|||||||
this.hooks = new Hooks();
|
this.hooks = new Hooks();
|
||||||
this.plugin_manager = new PluginManager(this, this.hooks);
|
this.plugin_manager = new PluginManager(this, this.hooks);
|
||||||
this.plugin_manager.registerAll(props.plugins);
|
this.plugin_manager.registerAll(props.plugins);
|
||||||
if (props.isWhiteboard) {
|
if (props.isEdgeless) {
|
||||||
this.isWhiteboard = true;
|
this.isEdgeless = true;
|
||||||
}
|
}
|
||||||
for (const [name, block] of Object.entries(props.views)) {
|
for (const [name, block] of Object.entries(props.views)) {
|
||||||
services.api.editorBlock.registerContentExporter(
|
services.api.editorBlock.registerContentExporter(
|
||||||
@ -148,18 +144,41 @@ export class Editor implements Virgo {
|
|||||||
public get container() {
|
public get container() {
|
||||||
return this.ui_container;
|
return this.ui_container;
|
||||||
}
|
}
|
||||||
// preference to use withSuspend
|
|
||||||
|
/**
|
||||||
|
* Use it discreetly.
|
||||||
|
* Preference to use {@link withBatch}
|
||||||
|
*/
|
||||||
public suspend(flag: boolean) {
|
public suspend(flag: boolean) {
|
||||||
services.api.editorBlock.suspend(this.workspace, flag);
|
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
|
fn: T
|
||||||
): Promise<Awaited<ReturnType<T>>> {
|
): Promise<Awaited<ReturnType<T>>> {
|
||||||
services.api.editorBlock.suspend(this.workspace, true);
|
return this.withBatch(fn)();
|
||||||
const result = await fn();
|
|
||||||
services.api.editorBlock.suspend(this.workspace, false);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setReactRenderRoot(props: {
|
public setReactRenderRoot(props: {
|
||||||
@ -211,7 +230,12 @@ export class Editor implements Virgo {
|
|||||||
return await services.api.editorBlock.update(patches);
|
return await services.api.editorBlock.update(patches);
|
||||||
},
|
},
|
||||||
remove: async ({ workspace, id }: WorkspaceAndBlockId) => {
|
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 (
|
observe: async (
|
||||||
{ workspace, id }: WorkspaceAndBlockId,
|
{ workspace, id }: WorkspaceAndBlockId,
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
import {
|
import {
|
||||||
|
debounce,
|
||||||
domToRect,
|
domToRect,
|
||||||
|
getBlockIdByDom,
|
||||||
|
last,
|
||||||
Point,
|
Point,
|
||||||
Rect,
|
Rect,
|
||||||
last,
|
|
||||||
without,
|
without,
|
||||||
debounce,
|
|
||||||
getBlockIdByDom,
|
|
||||||
} from '@toeverything/utils';
|
} from '@toeverything/utils';
|
||||||
import EventEmitter from 'eventemitter3';
|
import EventEmitter from 'eventemitter3';
|
||||||
|
|
||||||
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import { BlockEditor } from '../..';
|
import { BlockEditor } from '../..';
|
||||||
import { AsyncBlock } from '../block';
|
import { AsyncBlock } from '../block';
|
||||||
import { VirgoSelection } from '../types';
|
import { VirgoSelection } from '../types';
|
||||||
@ -18,19 +19,17 @@ import {
|
|||||||
changeEventName,
|
changeEventName,
|
||||||
CursorTypes,
|
CursorTypes,
|
||||||
IdList,
|
IdList,
|
||||||
|
SelectBlock,
|
||||||
selectEndEventName,
|
selectEndEventName,
|
||||||
SelectEventCallbackTypes,
|
SelectEventCallbackTypes,
|
||||||
SelectEventTypes,
|
SelectEventTypes,
|
||||||
|
SelectInfo,
|
||||||
SelectionSettings,
|
SelectionSettings,
|
||||||
SelectionSettingsMap,
|
SelectionSettingsMap,
|
||||||
SelectionTypes,
|
SelectionTypes,
|
||||||
SelectPosition,
|
SelectPosition,
|
||||||
SelectBlock,
|
|
||||||
SelectInfo,
|
|
||||||
} from './types';
|
} from './types';
|
||||||
import { isLikeBlockListIds } from './utils';
|
import { isLikeBlockListIds } from './utils';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
|
||||||
import { Editor } from 'slate';
|
|
||||||
// IMP: maybe merge active and select into single function
|
// IMP: maybe merge active and select into single function
|
||||||
|
|
||||||
export type SelectionInfo = InstanceType<
|
export type SelectionInfo = InstanceType<
|
||||||
@ -336,12 +335,12 @@ export class SelectionManager implements VirgoSelection {
|
|||||||
});
|
});
|
||||||
for await (const childBlock of selectableChildren) {
|
for await (const childBlock of selectableChildren) {
|
||||||
const { dom } = childBlock;
|
const { dom } = childBlock;
|
||||||
if (dom && selectionRect.isIntersect(domToRect(dom))) {
|
|
||||||
selectedNodes.push(childBlock);
|
|
||||||
}
|
|
||||||
if (!dom) {
|
if (!dom) {
|
||||||
console.warn('can not find dom bind with block');
|
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 just only has one selected maybe select the children
|
||||||
if (selectedNodes.length === 1) {
|
if (selectedNodes.length === 1) {
|
||||||
@ -1063,10 +1062,10 @@ export class SelectionManager implements VirgoSelection {
|
|||||||
index: number,
|
index: number,
|
||||||
blockId: string
|
blockId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let preRang = document.createRange();
|
const preRang = document.createRange();
|
||||||
preRang.setStart(nowRange.startContainer, index);
|
preRang.setStart(nowRange.startContainer, index);
|
||||||
preRang.setEnd(nowRange.endContainer, index);
|
preRang.setEnd(nowRange.endContainer, index);
|
||||||
let prePosition = preRang.getClientRects().item(0);
|
const prePosition = preRang.getClientRects().item(0);
|
||||||
this.activeNodeByNodeId(
|
this.activeNodeByNodeId(
|
||||||
blockId,
|
blockId,
|
||||||
new Point(prePosition.left, prePosition.bottom)
|
new Point(prePosition.left, prePosition.bottom)
|
||||||
|
@ -107,7 +107,7 @@ export interface Virgo {
|
|||||||
getBlockDomById: (id: string) => Promise<HTMLElement>;
|
getBlockDomById: (id: string) => Promise<HTMLElement>;
|
||||||
getBlockByPoint: (point: Point) => Promise<AsyncBlock>;
|
getBlockByPoint: (point: Point) => Promise<AsyncBlock>;
|
||||||
getGroupBlockByPoint: (point: Point) => Promise<AsyncBlock>;
|
getGroupBlockByPoint: (point: Point) => Promise<AsyncBlock>;
|
||||||
isWhiteboard: boolean;
|
isEdgeless: boolean;
|
||||||
mouseManager: MouseManager;
|
mouseManager: MouseManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,10 +9,10 @@ import {
|
|||||||
BlockDecoration,
|
BlockDecoration,
|
||||||
MapOperation,
|
MapOperation,
|
||||||
} from '@toeverything/datasource/jwt';
|
} from '@toeverything/datasource/jwt';
|
||||||
|
import { cloneDeep } from '@toeverything/utils';
|
||||||
import type { EventData } from '../block';
|
import type { EventData } from '../block';
|
||||||
import { AsyncBlock } from '../block';
|
import { AsyncBlock } from '../block';
|
||||||
import type { Editor } from '../editor';
|
import type { Editor } from '../editor';
|
||||||
import { cloneDeep } from '@toeverything/utils';
|
|
||||||
import { SelectBlock } from '../selection';
|
import { SelectBlock } from '../selection';
|
||||||
|
|
||||||
export interface CreateView {
|
export interface CreateView {
|
||||||
@ -114,7 +114,21 @@ export abstract class BaseView {
|
|||||||
// Whether the component is empty
|
// Whether the component is empty
|
||||||
isEmpty(block: AsyncBlock): boolean {
|
isEmpty(block: AsyncBlock): boolean {
|
||||||
const text = block.getProperty('text');
|
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 {
|
getSelProperties(block: AsyncBlock, selectInfo: any): DefaultColumnsValue {
|
||||||
|
@ -7,7 +7,6 @@ export enum RecastScene {
|
|||||||
Page = 'page',
|
Page = 'page',
|
||||||
Kanban = 'kanban',
|
Kanban = 'kanban',
|
||||||
Table = 'table',
|
Table = 'table',
|
||||||
// Whiteboard = 'whiteboard',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RecastViewId = string & {
|
export type RecastViewId = string & {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import { MuiClickAwayListener } from '@toeverything/components/ui';
|
||||||
import { BlockFlavorKeys, Protocol } from '@toeverything/datasource/db-service';
|
import { BlockFlavorKeys, Protocol } from '@toeverything/datasource/db-service';
|
||||||
|
import { HookType, PluginHooks, Virgo } from '@toeverything/framework/virgo';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -6,18 +8,15 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { QueryResult } from '../../search';
|
||||||
import { MuiClickAwayListener } from '@toeverything/components/ui';
|
|
||||||
import { Virgo, HookType, PluginHooks } from '@toeverything/framework/virgo';
|
|
||||||
|
|
||||||
import { CommandMenuContainer } from './Container';
|
|
||||||
import {
|
import {
|
||||||
CommandMenuCategories,
|
CommandMenuCategories,
|
||||||
commandMenuHandlerMap,
|
commandMenuHandlerMap,
|
||||||
commonCommandMenuHandler,
|
commonCommandMenuHandler,
|
||||||
menuItemsMap,
|
menuItemsMap,
|
||||||
} from './config';
|
} from './config';
|
||||||
import { QueryResult } from '../../search';
|
import { CommandMenuContainer } from './Container';
|
||||||
|
|
||||||
export type CommandMenuProps = {
|
export type CommandMenuProps = {
|
||||||
editor: Virgo;
|
editor: Virgo;
|
||||||
hooks: PluginHooks;
|
hooks: PluginHooks;
|
||||||
@ -225,7 +224,11 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
|
|||||||
await commonCommandMenuHandler(blockId, type, editor);
|
await commonCommandMenuHandler(blockId, type, editor);
|
||||||
}
|
}
|
||||||
const block = await editor.getBlockById(blockId);
|
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 {
|
} else {
|
||||||
if (Protocol.Block.Type[type as BlockFlavorKeys]) {
|
if (Protocol.Block.Type[type as BlockFlavorKeys]) {
|
||||||
const block = await editor.commands.blockCommands.convertBlock(
|
const block = await editor.commands.blockCommands.convertBlock(
|
||||||
|
@ -75,21 +75,6 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
|
|||||||
[editor, groupBlock]
|
[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 handleRootMouseLeave = useCallback(() => setGroupBlock(null), []);
|
||||||
|
|
||||||
const handleRootDragEnd = () => {
|
const handleRootDragEnd = () => {
|
||||||
@ -128,7 +113,6 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
|
|||||||
handleRootMouseMove,
|
handleRootMouseMove,
|
||||||
handleRootMouseDown,
|
handleRootMouseDown,
|
||||||
handleRootDragOver,
|
handleRootDragOver,
|
||||||
handleRootDrop,
|
|
||||||
handleRootMouseLeave,
|
handleRootMouseLeave,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -170,7 +154,11 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
|
|||||||
setShowMenu(false);
|
setShowMenu(false);
|
||||||
|
|
||||||
if (groupBlock) {
|
if (groupBlock) {
|
||||||
const unobserve = groupBlock.onUpdate(() => setGroupBlock(null));
|
const unobserve = groupBlock.onUpdate(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setGroupBlock(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
return unobserve;
|
return unobserve;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -14,7 +14,7 @@ export class GroupMenuPlugin extends BasePlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override _onRender(): void {
|
protected override _onRender(): void {
|
||||||
if (this.editor.isWhiteboard) return;
|
if (this.editor.isEdgeless) return;
|
||||||
this.root = new PluginRenderRoot({
|
this.root = new PluginRenderRoot({
|
||||||
name: PLUGIN_NAME,
|
name: PLUGIN_NAME,
|
||||||
render: this.editor.reactRenderRoot.render,
|
render: this.editor.reactRenderRoot.render,
|
||||||
|
@ -1,47 +1,47 @@
|
|||||||
/* eslint-disable max-lines */
|
/* 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 {
|
import {
|
||||||
fontBgColorPalette,
|
fontBgColorPalette,
|
||||||
fontColorPalette,
|
fontColorPalette,
|
||||||
type TextAlignOptions,
|
type TextAlignOptions,
|
||||||
} from '@toeverything/components/common';
|
} from '@toeverything/components/common';
|
||||||
import { Virgo } from '@toeverything/framework/virgo';
|
|
||||||
import { BlockFlavorKeys, Protocol } from '@toeverything/datasource/db-service';
|
|
||||||
import { ClickItemHandler, InlineMenuItem } from './types';
|
|
||||||
import {
|
import {
|
||||||
inlineMenuNamesKeys,
|
BacklinksIcon,
|
||||||
inlineMenuNamesForFontColor,
|
BulletIcon,
|
||||||
INLINE_MENU_UI_TYPES,
|
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,
|
inlineMenuNames,
|
||||||
|
inlineMenuNamesForFontColor,
|
||||||
|
inlineMenuNamesKeys,
|
||||||
|
INLINE_MENU_UI_TYPES,
|
||||||
} from './config';
|
} from './config';
|
||||||
|
import { ClickItemHandler, InlineMenuItem } from './types';
|
||||||
|
|
||||||
const convert_to_block_type = async ({
|
const convert_to_block_type = async ({
|
||||||
editor,
|
editor,
|
||||||
@ -732,27 +732,27 @@ export const getInlineMenuData = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: INLINE_MENU_UI_TYPES.dropdown,
|
type: INLINE_MENU_UI_TYPES.dropdown,
|
||||||
icon: AlignLeftIcon,
|
icon: TextAlignLeftIcon,
|
||||||
name: inlineMenuNames.currentTextAlign,
|
name: inlineMenuNames.currentTextAlign,
|
||||||
nameKey: inlineMenuNamesKeys.currentTextAlign,
|
nameKey: inlineMenuNamesKeys.currentTextAlign,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
type: INLINE_MENU_UI_TYPES.icon,
|
type: INLINE_MENU_UI_TYPES.icon,
|
||||||
icon: AlignLeftIcon,
|
icon: TextAlignLeftIcon,
|
||||||
name: inlineMenuNames.alignLeft,
|
name: inlineMenuNames.alignLeft,
|
||||||
nameKey: inlineMenuNamesKeys.alignLeft,
|
nameKey: inlineMenuNamesKeys.alignLeft,
|
||||||
onClick: common_handler_for_inline_menu,
|
onClick: common_handler_for_inline_menu,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: INLINE_MENU_UI_TYPES.icon,
|
type: INLINE_MENU_UI_TYPES.icon,
|
||||||
icon: AlignCenterIcon,
|
icon: TextAlignCenterIcon,
|
||||||
name: inlineMenuNames.alignCenter,
|
name: inlineMenuNames.alignCenter,
|
||||||
nameKey: inlineMenuNamesKeys.alignCenter,
|
nameKey: inlineMenuNamesKeys.alignCenter,
|
||||||
onClick: common_handler_for_inline_menu,
|
onClick: common_handler_for_inline_menu,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: INLINE_MENU_UI_TYPES.icon,
|
type: INLINE_MENU_UI_TYPES.icon,
|
||||||
icon: AlignRightIcon,
|
icon: TextAlignRightIcon,
|
||||||
name: inlineMenuNames.alignRight,
|
name: inlineMenuNames.alignRight,
|
||||||
nameKey: inlineMenuNamesKeys.alignRight,
|
nameKey: inlineMenuNamesKeys.alignRight,
|
||||||
onClick: common_handler_for_inline_menu,
|
onClick: common_handler_for_inline_menu,
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { HookType, BlockDropPlacement } from '@toeverything/framework/virgo';
|
import { BlockDropPlacement, HookType } 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 { domToRect, last, Point } from '@toeverything/utils';
|
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;
|
const DRAG_THROTTLE_DELAY = 60;
|
||||||
export class LeftMenuPlugin extends BasePlugin {
|
export class LeftMenuPlugin extends BasePlugin {
|
||||||
private _mousedown?: boolean;
|
private _mousedown?: boolean;
|
||||||
@ -111,6 +111,10 @@ export class LeftMenuPlugin extends BasePlugin {
|
|||||||
block.dom,
|
block.dom,
|
||||||
block.id
|
block.id
|
||||||
);
|
);
|
||||||
|
if (!targetBlock.dom) {
|
||||||
|
console.warn('Can not find dom bind with block', targetBlock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._lineInfo.next({
|
this._lineInfo.next({
|
||||||
direction,
|
direction,
|
||||||
blockInfo: {
|
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 './image/image';
|
||||||
export * from './format-clear/format-clear';
|
export * from './format-clear/format-clear';
|
||||||
export * from './backward-undo/backward-undo';
|
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-strikethrough/format-strikethrough';
|
||||||
export * from './format-color-text/format-color-text';
|
export * from './format-color-text/format-color-text';
|
||||||
export * from './format-background/format-background';
|
export * from './format-background/format-background';
|
||||||
export * from './align-left/align-left';
|
export * from './text-align-left/text-align-left';
|
||||||
export * from './align-center/align-center';
|
export * from './text-align-center/text-align-center';
|
||||||
export * from './align-right/align-right';
|
export * from './text-align-right/text-align-right';
|
||||||
export * from './align-justify/align-justify';
|
export * from './text-align-justify/text-align-justify';
|
||||||
export * from './arrow-drop-down/arrow-drop-down';
|
export * from './arrow-drop-down/arrow-drop-down';
|
||||||
export * from './arrow-right/arrow-right';
|
export * from './arrow-right/arrow-right';
|
||||||
export * from './more/more';
|
export * from './more/more';
|
||||||
@ -130,3 +130,17 @@ export * from './more-tags-an-subblocks/more-tags-an-subblocks';
|
|||||||
export * from './page-in-page-tree/page-in-page-tree';
|
export * from './page-in-page-tree/page-in-page-tree';
|
||||||
export * from './pin/pin';
|
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
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { SvgIcon, SvgIconProps } from '@mui/material';
|
import { SvgIcon, SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export interface AlignCenterIconProps extends Omit<SvgIconProps, 'color'> {
|
export interface TextAlignCenterIconProps extends Omit<SvgIconProps, 'color'> {
|
||||||
color?: string
|
color?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AlignCenterIcon = ({ color, style, ...props}: AlignCenterIconProps) => {
|
export const TextAlignCenterIcon = ({ color, style, ...props}: TextAlignCenterIconProps) => {
|
||||||
const propsStyles = {"color": color};
|
const propsStyles = {"color": color};
|
||||||
const customStyles = {};
|
const customStyles = {};
|
||||||
const styles = {...propsStyles, ...customStyles, ...style}
|
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
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { SvgIcon, SvgIconProps } from '@mui/material';
|
import { SvgIcon, SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export interface AlignJustifyIconProps extends Omit<SvgIconProps, 'color'> {
|
export interface TextAlignJustifyIconProps extends Omit<SvgIconProps, 'color'> {
|
||||||
color?: string
|
color?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AlignJustifyIcon = ({ color, style, ...props}: AlignJustifyIconProps) => {
|
export const TextAlignJustifyIcon = ({ color, style, ...props}: TextAlignJustifyIconProps) => {
|
||||||
const propsStyles = {"color": color};
|
const propsStyles = {"color": color};
|
||||||
const customStyles = {};
|
const customStyles = {};
|
||||||
const styles = {...propsStyles, ...customStyles, ...style}
|
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
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { SvgIcon, SvgIconProps } from '@mui/material';
|
import { SvgIcon, SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export interface AlignLeftIconProps extends Omit<SvgIconProps, 'color'> {
|
export interface TextAlignLeftIconProps extends Omit<SvgIconProps, 'color'> {
|
||||||
color?: string
|
color?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AlignLeftIcon = ({ color, style, ...props}: AlignLeftIconProps) => {
|
export const TextAlignLeftIcon = ({ color, style, ...props}: TextAlignLeftIconProps) => {
|
||||||
const propsStyles = {"color": color};
|
const propsStyles = {"color": color};
|
||||||
const customStyles = {};
|
const customStyles = {};
|
||||||
const styles = {...propsStyles, ...customStyles, ...style}
|
const styles = {...propsStyles, ...customStyles, ...style}
|
Before Width: | Height: | Size: 190 B After Width: | Height: | Size: 190 B |