feat: workflow add note node (#5164)

This commit is contained in:
zxhlyh 2024-06-14 17:08:11 +08:00 committed by GitHub
parent d7fbae286a
commit c28d709d7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 2375 additions and 169 deletions

View File

@ -0,0 +1,5 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon">
<path id="Icon_2" d="M4.5 7.99996H9.83333C11.3061 7.99996 12.5 6.80605 12.5 5.33329C12.5 3.86053 11.3061 2.66663 9.83333 2.66663H4.5V7.99996ZM4.5 7.99996H10.5C11.9728 7.99996 13.1667 9.19387 13.1667 10.6666C13.1667 12.1394 11.9728 13.3333 10.5 13.3333H4.5V7.99996Z" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 473 B

View File

@ -0,0 +1,5 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon">
<path id="Icon_2" d="M14.5 8.00004L6.5 8.00004M14.5 4.00004L6.5 4.00004M14.5 12L6.5 12M3.83333 8.00004C3.83333 8.36823 3.53486 8.66671 3.16667 8.66671C2.79848 8.66671 2.5 8.36823 2.5 8.00004C2.5 7.63185 2.79848 7.33337 3.16667 7.33337C3.53486 7.33337 3.83333 7.63185 3.83333 8.00004ZM3.83333 4.00004C3.83333 4.36823 3.53486 4.66671 3.16667 4.66671C2.79848 4.66671 2.5 4.36823 2.5 4.00004C2.5 3.63185 2.79848 3.33337 3.16667 3.33337C3.53486 3.33337 3.83333 3.63185 3.83333 4.00004ZM3.83333 12C3.83333 12.3682 3.53486 12.6667 3.16667 12.6667C2.79848 12.6667 2.5 12.3682 2.5 12C2.5 11.6319 2.79848 11.3334 3.16667 11.3334C3.53486 11.3334 3.83333 11.6319 3.83333 12Z" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 871 B

View File

@ -0,0 +1,3 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.1666 2.66663H7.16659M9.83325 13.3333H3.83325M10.4999 2.66663L6.49992 13.3333" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 279 B

View File

@ -0,0 +1,5 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon">
<path id="Icon_2" d="M4.5 10.6666C4.5 12.1394 5.69391 13.3333 7.16667 13.3333H9.83333C11.3061 13.3333 12.5 12.1394 12.5 10.6666C12.5 9.19387 11.3061 7.99996 9.83333 7.99996M12.5 5.33329C12.5 3.86053 11.3061 2.66663 9.83333 2.66663H7.16667C5.69391 2.66663 4.5 3.86053 4.5 5.33329M2.5 7.99996H14.5" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 504 B

View File

@ -0,0 +1,8 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon">
<g id="Vector">
<path d="M2.0922 12.4865C2.57616 12.4865 2.84839 12.2445 3.01778 11.6638L3.47754 10.3026H6.62933L7.0891 11.6819C7.25243 12.2506 7.52463 12.4865 8.03887 12.4865C8.5712 12.4865 8.95232 12.1295 8.95232 11.6275C8.95232 11.4459 8.92208 11.2827 8.83743 11.0467L6.44179 4.54954C6.18167 3.83569 5.7582 3.52112 5.04436 3.52112C4.35471 3.52112 3.9252 3.84779 3.67112 4.55559L1.28762 11.0467C1.20897 11.2705 1.16663 11.4762 1.16663 11.6275C1.16663 12.1538 1.52355 12.4865 2.0922 12.4865ZM3.8768 8.88703L5.00806 5.31177H5.05041L6.20586 8.88703H3.8768Z" fill="#344054"/>
<path d="M12.1068 12.4744C12.9174 12.4744 13.7281 12.0691 14.091 11.3795H14.1273V11.7122C14.1636 12.2324 14.4963 12.4986 14.9742 12.4986C15.4764 12.4986 15.8333 12.1961 15.8333 11.6093V7.91309C15.8333 6.60636 14.7504 5.74734 13.0868 5.74734C11.7438 5.74734 10.7033 6.22525 10.4008 6.99957C10.3403 7.13269 10.3101 7.25973 10.3101 7.39885C10.3101 7.79813 10.6186 8.07638 11.0481 8.07638C11.3324 8.07638 11.5563 7.9675 11.7499 7.74973C12.1431 7.24157 12.4697 7.06613 13.0081 7.06613C13.6736 7.06613 14.0971 7.41701 14.0971 8.02198V8.4515L12.4637 8.54823C10.8424 8.64503 9.93506 9.32864 9.93506 10.5083C9.93506 11.6759 10.8727 12.4744 12.1068 12.4744ZM12.6876 11.1979C12.0947 11.1979 11.6954 10.8955 11.6954 10.4115C11.6954 9.95176 12.0705 9.65528 12.7299 9.60695L14.0971 9.52224V9.99408C14.0971 10.6958 13.4619 11.1979 12.6876 11.1979Z" fill="#344054"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="sticker-square">
<path id="Icon" d="M8.66667 2.33333V4.13333C8.66667 5.25344 8.66667 5.81349 8.88465 6.24131C9.0764 6.61764 9.38236 6.9236 9.75869 7.11535C10.1865 7.33333 10.7466 7.33333 11.8667 7.33333H13.6667M14 8.65882V10.8C14 11.9201 14 12.4802 13.782 12.908C13.5903 13.2843 13.2843 13.5903 12.908 13.782C12.4802 14 11.9201 14 10.8 14H5.2C4.0799 14 3.51984 14 3.09202 13.782C2.71569 13.5903 2.40973 13.2843 2.21799 12.908C2 12.4802 2 11.9201 2 10.8V5.2C2 4.0799 2 3.51984 2.21799 3.09202C2.40973 2.71569 2.71569 2.40973 3.09202 2.21799C3.51984 2 4.0799 2 5.2 2H7.34118C7.83036 2 8.07496 2 8.30513 2.05526C8.5092 2.10425 8.70429 2.18506 8.88324 2.29472C9.08507 2.4184 9.25802 2.59135 9.60393 2.93726L13.0627 6.39608C13.4086 6.74198 13.5816 6.91493 13.7053 7.11676C13.8149 7.29571 13.8957 7.4908 13.9447 7.69487C14 7.92505 14 8.16964 14 8.65882Z" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,5 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon">
<path id="Icon_2" d="M8.97167 12.2427L8.02886 13.1855C6.72711 14.4872 4.61656 14.4872 3.31481 13.1855C2.01306 11.8837 2.01306 9.77317 3.31481 8.47142L4.25762 7.52861M12.7429 8.47142L13.6857 7.52861C14.9875 6.22687 14.9875 4.11632 13.6857 2.81457C12.384 1.51282 10.2734 1.51282 8.97167 2.81457L8.02886 3.75738M6.16693 10.3333L10.8336 5.66667" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 549 B

View File

@ -0,0 +1,10 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Left Icon" clip-path="url(#clip0_6246_47371)">
<path id="Icon" d="M4.5 2V1M7.5 10V11M2 4.5H1M10 7.5H11M2.45711 2.45711L1.75 1.75M9.54289 9.54289L10.25 10.25M6 8.82843L4.93934 9.88909C4.15829 10.6701 2.89196 10.6701 2.11091 9.88909C1.32986 9.10804 1.32986 7.84171 2.11091 7.06066L3.17157 6M8.82843 6L9.88909 4.93934C10.6701 4.15829 10.6701 2.89196 9.88909 2.11091C9.10804 1.32986 7.84171 1.32986 7.06066 2.11091L6 3.17157" stroke="#667085" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_6246_47371">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 727 B

View File

@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "17",
"height": "16",
"viewBox": "0 0 17 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon_2",
"d": "M4.5 7.99996H9.83333C11.3061 7.99996 12.5 6.80605 12.5 5.33329C12.5 3.86053 11.3061 2.66663 9.83333 2.66663H4.5V7.99996ZM4.5 7.99996H10.5C11.9728 7.99996 13.1667 9.19387 13.1667 10.6666C13.1667 12.1394 11.9728 13.3333 10.5 13.3333H4.5V7.99996Z",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "Bold01"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Bold01.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Bold01'
export default Icon

View File

@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "17",
"height": "16",
"viewBox": "0 0 17 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon_2",
"d": "M14.5 8.00004L6.5 8.00004M14.5 4.00004L6.5 4.00004M14.5 12L6.5 12M3.83333 8.00004C3.83333 8.36823 3.53486 8.66671 3.16667 8.66671C2.79848 8.66671 2.5 8.36823 2.5 8.00004C2.5 7.63185 2.79848 7.33337 3.16667 7.33337C3.53486 7.33337 3.83333 7.63185 3.83333 8.00004ZM3.83333 4.00004C3.83333 4.36823 3.53486 4.66671 3.16667 4.66671C2.79848 4.66671 2.5 4.36823 2.5 4.00004C2.5 3.63185 2.79848 3.33337 3.16667 3.33337C3.53486 3.33337 3.83333 3.63185 3.83333 4.00004ZM3.83333 12C3.83333 12.3682 3.53486 12.6667 3.16667 12.6667C2.79848 12.6667 2.5 12.3682 2.5 12C2.5 11.6319 2.79848 11.3334 3.16667 11.3334C3.53486 11.3334 3.83333 11.6319 3.83333 12Z",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "Dotpoints01"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Dotpoints01.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Dotpoints01'
export default Icon

View File

@ -0,0 +1,29 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "17",
"height": "16",
"viewBox": "0 0 17 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M13.1666 2.66663H7.16659M9.83325 13.3333H3.83325M10.4999 2.66663L6.49992 13.3333",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
},
"name": "Italic01"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Italic01.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Italic01'
export default Icon

View File

@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "17",
"height": "16",
"viewBox": "0 0 17 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon_2",
"d": "M4.5 10.6666C4.5 12.1394 5.69391 13.3333 7.16667 13.3333H9.83333C11.3061 13.3333 12.5 12.1394 12.5 10.6666C12.5 9.19387 11.3061 7.99996 9.83333 7.99996M12.5 5.33329C12.5 3.86053 11.3061 2.66663 9.83333 2.66663H7.16667C5.69391 2.66663 4.5 3.86053 4.5 5.33329M2.5 7.99996H14.5",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "Strikethrough01"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Strikethrough01.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Strikethrough01'
export default Icon

View File

@ -0,0 +1,53 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "17",
"height": "16",
"viewBox": "0 0 17 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Vector"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M2.0922 12.4865C2.57616 12.4865 2.84839 12.2445 3.01778 11.6638L3.47754 10.3026H6.62933L7.0891 11.6819C7.25243 12.2506 7.52463 12.4865 8.03887 12.4865C8.5712 12.4865 8.95232 12.1295 8.95232 11.6275C8.95232 11.4459 8.92208 11.2827 8.83743 11.0467L6.44179 4.54954C6.18167 3.83569 5.7582 3.52112 5.04436 3.52112C4.35471 3.52112 3.9252 3.84779 3.67112 4.55559L1.28762 11.0467C1.20897 11.2705 1.16663 11.4762 1.16663 11.6275C1.16663 12.1538 1.52355 12.4865 2.0922 12.4865ZM3.8768 8.88703L5.00806 5.31177H5.05041L6.20586 8.88703H3.8768Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M12.1068 12.4744C12.9174 12.4744 13.7281 12.0691 14.091 11.3795H14.1273V11.7122C14.1636 12.2324 14.4963 12.4986 14.9742 12.4986C15.4764 12.4986 15.8333 12.1961 15.8333 11.6093V7.91309C15.8333 6.60636 14.7504 5.74734 13.0868 5.74734C11.7438 5.74734 10.7033 6.22525 10.4008 6.99957C10.3403 7.13269 10.3101 7.25973 10.3101 7.39885C10.3101 7.79813 10.6186 8.07638 11.0481 8.07638C11.3324 8.07638 11.5563 7.9675 11.7499 7.74973C12.1431 7.24157 12.4697 7.06613 13.0081 7.06613C13.6736 7.06613 14.0971 7.41701 14.0971 8.02198V8.4515L12.4637 8.54823C10.8424 8.64503 9.93506 9.32864 9.93506 10.5083C9.93506 11.6759 10.8727 12.4744 12.1068 12.4744ZM12.6876 11.1979C12.0947 11.1979 11.6954 10.8955 11.6954 10.4115C11.6954 9.95176 12.0705 9.65528 12.7299 9.60695L14.0971 9.52224V9.99408C14.0971 10.6958 13.4619 11.1979 12.6876 11.1979Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
}
]
},
"name": "TitleCase"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './TitleCase.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'TitleCase'
export default Icon

View File

@ -1,11 +1,16 @@
export { default as AlignLeft } from './AlignLeft'
export { default as BezierCurve03 } from './BezierCurve03'
export { default as Bold01 } from './Bold01'
export { default as Colors } from './Colors'
export { default as Cursor02C } from './Cursor02C'
export { default as Dotpoints01 } from './Dotpoints01'
export { default as Hand02 } from './Hand02'
export { default as ImageIndentLeft } from './ImageIndentLeft'
export { default as Italic01 } from './Italic01'
export { default as LeftIndent02 } from './LeftIndent02'
export { default as LetterSpacing01 } from './LetterSpacing01'
export { default as Strikethrough01 } from './Strikethrough01'
export { default as TitleCase } from './TitleCase'
export { default as TypeSquare } from './TypeSquare'
export { default as ZoomIn } from './ZoomIn'
export { default as ZoomOut } from './ZoomOut'

View File

@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "sticker-square"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M8.66667 2.33333V4.13333C8.66667 5.25344 8.66667 5.81349 8.88465 6.24131C9.0764 6.61764 9.38236 6.9236 9.75869 7.11535C10.1865 7.33333 10.7466 7.33333 11.8667 7.33333H13.6667M14 8.65882V10.8C14 11.9201 14 12.4802 13.782 12.908C13.5903 13.2843 13.2843 13.5903 12.908 13.782C12.4802 14 11.9201 14 10.8 14H5.2C4.0799 14 3.51984 14 3.09202 13.782C2.71569 13.5903 2.40973 13.2843 2.21799 12.908C2 12.4802 2 11.9201 2 10.8V5.2C2 4.0799 2 3.51984 2.21799 3.09202C2.40973 2.71569 2.71569 2.40973 3.09202 2.21799C3.51984 2 4.0799 2 5.2 2H7.34118C7.83036 2 8.07496 2 8.30513 2.05526C8.5092 2.10425 8.70429 2.18506 8.88324 2.29472C9.08507 2.4184 9.25802 2.59135 9.60393 2.93726L13.0627 6.39608C13.4086 6.74198 13.5816 6.91493 13.7053 7.11676C13.8149 7.29571 13.8957 7.4908 13.9447 7.69487C14 7.92505 14 8.16964 14 8.65882Z",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "StickerSquare"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './StickerSquare.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'StickerSquare'
export default Icon

View File

@ -9,3 +9,4 @@ export { default as FilePlus02 } from './FilePlus02'
export { default as FileText } from './FileText'
export { default as FileUpload } from './FileUpload'
export { default as Folder } from './Folder'
export { default as StickerSquare } from './StickerSquare'

View File

@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "17",
"height": "16",
"viewBox": "0 0 17 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon_2",
"d": "M8.97167 12.2427L8.02886 13.1855C6.72711 14.4872 4.61656 14.4872 3.31481 13.1855C2.01306 11.8837 2.01306 9.77317 3.31481 8.47142L4.25762 7.52861M12.7429 8.47142L13.6857 7.52861C14.9875 6.22687 14.9875 4.11632 13.6857 2.81457C12.384 1.51282 10.2734 1.51282 8.97167 2.81457L8.02886 3.75738M6.16693 10.3333L10.8336 5.66667",
"stroke": "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "Link01"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Link01.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Link01'
export default Icon

View File

@ -0,0 +1,66 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Left Icon",
"clip-path": "url(#clip0_6246_47371)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M4.5 2V1M7.5 10V11M2 4.5H1M10 7.5H11M2.45711 2.45711L1.75 1.75M9.54289 9.54289L10.25 10.25M6 8.82843L4.93934 9.88909C4.15829 10.6701 2.89196 10.6701 2.11091 9.88909C1.32986 9.10804 1.32986 7.84171 2.11091 7.06066L3.17157 6M8.82843 6L9.88909 4.93934C10.6701 4.15829 10.6701 2.89196 9.88909 2.11091C9.10804 1.32986 7.84171 1.32986 7.06066 2.11091L6 3.17157",
"stroke": "currentColor",
"stroke-width": "1.25",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_6246_47371"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "12",
"height": "12",
"fill": "white"
},
"children": []
}
]
}
]
}
]
},
"name": "LinkBroken01"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './LinkBroken01.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'LinkBroken01'
export default Icon

View File

@ -14,7 +14,9 @@ export { default as Edit05 } from './Edit05'
export { default as Hash02 } from './Hash02'
export { default as HelpCircle } from './HelpCircle'
export { default as InfoCircle } from './InfoCircle'
export { default as Link01 } from './Link01'
export { default as Link03 } from './Link03'
export { default as LinkBroken01 } from './LinkBroken01'
export { default as LinkExternal01 } from './LinkExternal01'
export { default as LinkExternal02 } from './LinkExternal02'
export { default as Loading02 } from './Loading02'

View File

@ -12,7 +12,11 @@ import {
useStore,
useWorkflowStore,
} from './store'
import { useNodesInteractions } from './hooks'
import { CUSTOM_NODE } from './constants'
import CustomNode from './nodes'
import CustomNoteNode from './note-node'
import { CUSTOM_NOTE_NODE } from './note-node/constants'
const CandidateNode = () => {
const store = useStoreApi()
@ -21,6 +25,7 @@ const CandidateNode = () => {
const candidateNode = useStore(s => s.candidateNode)
const mousePosition = useStore(s => s.mousePosition)
const { zoom } = useViewport()
const { handleNodeSelect } = useNodesInteractions()
useEventListener('click', (e) => {
const { candidateNode, mousePosition } = workflowStore.getState()
@ -49,6 +54,9 @@ const CandidateNode = () => {
})
setNodes(newNodes)
workflowStore.setState({ candidateNode: undefined })
if (candidateNode.type === CUSTOM_NOTE_NODE)
handleNodeSelect(candidateNode.id)
}
})
@ -73,7 +81,16 @@ const CandidateNode = () => {
transformOrigin: '0 0',
}}
>
<CustomNode {...candidateNode as any} />
{
candidateNode.type === CUSTOM_NODE && (
<CustomNode {...candidateNode as any} />
)
}
{
candidateNode.type === CUSTOM_NOTE_NODE && (
<CustomNoteNode {...candidateNode as any} />
)
}
</div>
)
}

View File

@ -391,3 +391,4 @@ export const PARAMETER_EXTRACTOR_COMMON_STRUCT: Var[] = [
]
export const WORKFLOW_DATA_UPDATE = 'WORKFLOW_DATA_UPDATE'
export const CUSTOM_NODE = 'custom'

View File

@ -14,7 +14,10 @@ import {
getToolCheckParams,
getValidTreeNodes,
} from '../utils'
import { MAX_TREE_DEEPTH } from '../constants'
import {
CUSTOM_NODE,
MAX_TREE_DEEPTH,
} from '../constants'
import type { ToolNodeType } from '../nodes/tool/types'
import { useIsChatMode } from './use-workflow'
import { useNodesExtraData } from './use-nodes-data'
@ -33,7 +36,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
const needWarningNodes = useMemo(() => {
const list = []
const { validNodes } = getValidTreeNodes(nodes, edges)
const { validNodes } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges)
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
@ -53,17 +56,20 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
if (provider_type === CollectionType.workflow)
toolIcon = workflowTools.find(tool => tool.id === node.data.provider_id)?.icon
}
const { errorMessage } = nodesExtraData[node.data.type].checkValid(node.data, t, moreDataForCheckValid)
if (errorMessage || !validNodes.find(n => n.id === node.id)) {
list.push({
id: node.id,
type: node.data.type,
title: node.data.title,
toolIcon,
unConnected: !validNodes.find(n => n.id === node.id),
errorMessage,
})
if (node.type === CUSTOM_NODE) {
const { errorMessage } = nodesExtraData[node.data.type].checkValid(node.data, t, moreDataForCheckValid)
if (errorMessage || !validNodes.find(n => n.id === node.id)) {
list.push({
id: node.id,
type: node.data.type,
title: node.data.title,
toolIcon,
unConnected: !validNodes.find(n => n.id === node.id),
errorMessage,
})
}
}
}
@ -107,11 +113,11 @@ export const useChecklistBeforePublish = () => {
getNodes,
edges,
} = store.getState()
const nodes = getNodes()
const nodes = getNodes().filter(node => node.type === CUSTOM_NODE)
const {
validNodes,
maxDepth,
} = getValidTreeNodes(nodes, edges)
} = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges)
if (maxDepth > MAX_TREE_DEEPTH) {
notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEEPTH }) })

View File

@ -22,7 +22,8 @@ export const useNodeDataUpdate = () => {
const newNodes = produce(getNodes(), (draft) => {
const currentNode = draft.find(node => node.id === id)!
currentNode.data = { ...currentNode?.data, ...data }
if (currentNode)
currentNode.data = { ...currentNode.data, ...data }
})
setNodes(newNodes)
}, [store])

View File

@ -38,6 +38,7 @@ import {
getNodesConnectedSourceOrTargetHandleIdsMap,
getTopLeftNodePosition,
} from '../utils'
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
import type { IterationNodeType } from '../nodes/iteration/types'
import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types'
import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions'
@ -71,7 +72,7 @@ export const useNodesInteractions = () => {
if (getNodesReadOnly())
return
if (node.data.isIterationStart)
if (node.data.isIterationStart || node.type === CUSTOM_NOTE_NODE)
return
dragNodeStartPosition.current = { x: node.position.x, y: node.position.y }
@ -143,6 +144,9 @@ export const useNodesInteractions = () => {
if (getNodesReadOnly())
return
if (node.type === CUSTOM_NOTE_NODE)
return
const {
getNodes,
setNodes,
@ -193,10 +197,13 @@ export const useNodesInteractions = () => {
setEdges(newEdges)
}, [store, workflowStore, getNodesReadOnly])
const handleNodeLeave = useCallback<NodeMouseHandler>(() => {
const handleNodeLeave = useCallback<NodeMouseHandler>((_, node) => {
if (getNodesReadOnly())
return
if (node.type === CUSTOM_NOTE_NODE)
return
const {
setEnteringNodePayload,
} = workflowStore.getState()
@ -298,6 +305,9 @@ export const useNodesInteractions = () => {
if (targetNode?.data.isIterationStart)
return
if (sourceNode?.type === CUSTOM_NOTE_NODE || targetNode?.type === CUSTOM_NOTE_NODE)
return
const needDeleteEdges = edges.filter((edge) => {
if (
(edge.source === source && edge.sourceHandle === sourceHandle)
@ -361,6 +371,9 @@ export const useNodesInteractions = () => {
const { getNodes } = store.getState()
const node = getNodes().find(n => n.id === nodeId)!
if (node.type === CUSTOM_NOTE_NODE)
return
if (node.data.type === BlockEnum.VariableAggregator || node.data.type === BlockEnum.VariableAssigner) {
if (handleType === 'target')
return
@ -975,6 +988,9 @@ export const useNodesInteractions = () => {
}, [store])
const handleNodeContextMenu = useCallback((e: MouseEvent, node: Node) => {
if (node.type === CUSTOM_NOTE_NODE)
return
e.preventDefault()
const container = document.querySelector('#workflow-container')
const { x, y } = container!.getBoundingClientRect()
@ -1051,6 +1067,7 @@ export const useNodesInteractions = () => {
const nodeType = nodeToPaste.data.type
const newNode = generateNewNode({
type: nodeToPaste.type,
data: {
...NODES_INITIAL_DATA[nodeType],
...nodeToPaste.data,

View File

@ -34,8 +34,10 @@ import {
useWorkflowStore,
} from '../store'
import {
CUSTOM_NODE,
SUPPORT_OUTPUT_VARS_NODE,
} from '../constants'
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
import { useNodesExtraData } from './use-nodes-data'
import { useWorkflowTemplate } from './use-workflow-template'
@ -88,7 +90,7 @@ export const useWorkflow = () => {
const rankMap = {} as Record<string, Node>
nodes.forEach((node) => {
if (!node.parentId) {
if (!node.parentId && node.type === CUSTOM_NODE) {
const rank = layout.node(node.id).rank!
if (!rankMap[rank]) {
@ -103,7 +105,7 @@ export const useWorkflow = () => {
const newNodes = produce(nodes, (draft) => {
draft.forEach((node) => {
if (!node.parentId) {
if (!node.parentId && node.type === CUSTOM_NODE) {
const nodeWithPosition = layout.node(node.id)
node.position = {
@ -345,6 +347,9 @@ export const useWorkflow = () => {
if (targetNode.data.isIterationStart)
return false
if (sourceNode.type === CUSTOM_NOTE_NODE || targetNode.type === CUSTOM_NOTE_NODE)
return false
if (sourceNode && targetNode) {
const sourceNodeAvailableNextNodes = nodesExtraData[sourceNode.data.type].availableNextNodes
const targetNodeAvailablePrevNodes = [...nodesExtraData[targetNode.data.type].availablePrevNodes, BlockEnum.Start]

View File

@ -46,6 +46,8 @@ import {
} from './hooks'
import Header from './header'
import CustomNode from './nodes'
import CustomNoteNode from './note-node'
import { CUSTOM_NOTE_NODE } from './note-node/constants'
import Operator from './operator'
import CustomEdge from './custom-edge'
import CustomConnectionLine from './custom-connection-line'
@ -66,6 +68,7 @@ import {
initialNodes,
} from './utils'
import {
CUSTOM_NODE,
ITERATION_CHILDREN_Z_INDEX,
WORKFLOW_DATA_UPDATE,
} from './constants'
@ -76,10 +79,11 @@ import { useEventEmitterContextContext } from '@/context/event-emitter'
import Confirm from '@/app/components/base/confirm/common'
const nodeTypes = {
custom: CustomNode,
[CUSTOM_NODE]: CustomNode,
[CUSTOM_NOTE_NODE]: CustomNoteNode,
}
const edgeTypes = {
custom: CustomEdge,
[CUSTOM_NODE]: CustomEdge,
}
type WorkflowProps = {

View File

@ -19,10 +19,18 @@ const Icon = () => {
type NodeResizerProps = {
nodeId: string
nodeData: CommonNodeType
icon?: JSX.Element
minWidth?: number
minHeight?: number
maxWidth?: number
}
const NodeResizer = ({
nodeId,
nodeData,
icon = <Icon />,
minWidth = 272,
minHeight = 176,
maxWidth,
}: NodeResizerProps) => {
const { handleNodeResize } = useNodesInteractions()
@ -39,10 +47,11 @@ const NodeResizer = ({
position='bottom-right'
className='!border-none !bg-transparent'
onResize={handleResize}
minWidth={272}
minHeight={176}
minWidth={minWidth}
minHeight={minHeight}
maxWidth={maxWidth}
>
<div className='absolute bottom-[1px] right-[1px]'><Icon /></div>
<div className='absolute bottom-[1px] right-[1px]'>{icon}</div>
</NodeResizeControl>
</div>
)

View File

@ -64,3 +64,5 @@ export const PanelComponentMap: Record<string, ComponentType<any>> = {
[BlockEnum.ParameterExtractor]: ParameterExtractorPanel,
[BlockEnum.Iteration]: IterationPanel,
}
export const CUSTOM_NODE_TYPE = 'custom'

View File

@ -1,6 +1,10 @@
import { memo } from 'react'
import {
memo,
useMemo,
} from 'react'
import type { NodeProps } from 'reactflow'
import type { Node } from '../types'
import { CUSTOM_NODE } from '../constants'
import {
NodeComponentMap,
PanelComponentMap,
@ -23,14 +27,24 @@ const CustomNode = (props: NodeProps) => {
CustomNode.displayName = 'CustomNode'
export const Panel = memo((props: Node) => {
const nodeClass = props.type
const nodeData = props.data
const PanelComponent = PanelComponentMap[nodeData.type]
const PanelComponent = useMemo(() => {
if (nodeClass === CUSTOM_NODE)
return PanelComponentMap[nodeData.type]
return (
<BasePanel key={props.id} {...props}>
<PanelComponent />
</BasePanel>
)
return () => null
}, [nodeClass, nodeData.type])
if (nodeClass === CUSTOM_NODE) {
return (
<BasePanel key={props.id} {...props}>
<PanelComponent />
</BasePanel>
)
}
return null
})
Panel.displayName = 'Panel'

View File

@ -0,0 +1,42 @@
import { NoteTheme } from './types'
export const CUSTOM_NOTE_NODE = 'custom-note'
export const THEME_MAP: Record<string, { outer: string; title: string; bg: string; border: string }> = {
[NoteTheme.blue]: {
outer: '#2E90FA',
title: '#D1E9FF',
bg: '#EFF8FF',
border: '#84CAFF',
},
[NoteTheme.cyan]: {
outer: '#06AED4',
title: '#CFF9FE',
bg: '#ECFDFF',
border: '#67E3F9',
},
[NoteTheme.green]: {
outer: '#16B364',
title: '#D3F8DF',
bg: '#EDFCF2',
border: '#73E2A3',
},
[NoteTheme.yellow]: {
outer: '#EAAA08',
title: '#FEF7C3',
bg: '#FEFBE8',
border: '#FDE272',
},
[NoteTheme.pink]: {
outer: '#EE46BC',
title: '#FCE7F6',
bg: '#FDF2FA',
border: '#FAA7E0',
},
[NoteTheme.violet]: {
outer: '#875BF7',
title: '#ECE9FE',
bg: '#F5F3FF',
border: '#C3B5FD',
},
}

View File

@ -0,0 +1,29 @@
import { useCallback } from 'react'
import type { EditorState } from 'lexical'
import { useNodeDataUpdate } from '../hooks'
import type { NoteTheme } from './types'
export const useNote = (id: string) => {
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
const handleThemeChange = useCallback((theme: NoteTheme) => {
handleNodeDataUpdateWithSyncDraft({ id, data: { theme } })
}, [handleNodeDataUpdateWithSyncDraft, id])
const handleEditorChange = useCallback((editorState: EditorState) => {
if (!editorState?.isEmpty())
handleNodeDataUpdateWithSyncDraft({ id, data: { text: JSON.stringify(editorState) } })
else
handleNodeDataUpdateWithSyncDraft({ id, data: { text: '' } })
}, [handleNodeDataUpdateWithSyncDraft, id])
const handleShowAuthorChange = useCallback((showAuthor: boolean) => {
handleNodeDataUpdateWithSyncDraft({ id, data: { showAuthor } })
}, [handleNodeDataUpdateWithSyncDraft, id])
return {
handleThemeChange,
handleEditorChange,
handleShowAuthorChange,
}
}

View File

@ -0,0 +1,127 @@
import {
memo,
useCallback,
useRef,
} from 'react'
import cn from 'classnames'
import { useTranslation } from 'react-i18next'
import { useClickAway } from 'ahooks'
import type { NodeProps } from 'reactflow'
import NodeResizer from '../nodes/_base/components/node-resizer'
import {
useNodeDataUpdate,
useNodesInteractions,
} from '../hooks'
import { useStore } from '../store'
import {
NoteEditor,
NoteEditorContextProvider,
NoteEditorToolbar,
} from './note-editor'
import { THEME_MAP } from './constants'
import { useNote } from './hooks'
import type { NoteNodeType } from './types'
const Icon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
<path fillRule="evenodd" clipRule="evenodd" d="M12 9.75V6H13.5V9.75C13.5 11.8211 11.8211 13.5 9.75 13.5H6V12H9.75C10.9926 12 12 10.9926 12 9.75Z" fill="black" fillOpacity="0.16"/>
</svg>
)
}
const NoteNode = ({
id,
data,
}: NodeProps<NoteNodeType>) => {
const { t } = useTranslation()
const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey)
const ref = useRef<HTMLDivElement | null>(null)
const theme = data.theme
const {
handleThemeChange,
handleEditorChange,
handleShowAuthorChange,
} = useNote(id)
const {
handleNodesCopy,
handleNodesDuplicate,
handleNodeDelete,
} = useNodesInteractions()
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
const handleDeleteNode = useCallback(() => {
handleNodeDelete(id)
}, [id, handleNodeDelete])
useClickAway(() => {
handleNodeDataUpdateWithSyncDraft({ id, data: { selected: false } })
}, ref)
return (
<div
className={cn(
'flex flex-col relative rounded-md shadow-xs border hover:shadow-md',
)}
style={{
background: THEME_MAP[theme].bg,
borderColor: data.selected ? THEME_MAP[theme].border : 'rgba(0, 0, 0, 0.05)',
width: data.width,
height: data.height,
}}
ref={ref}
>
<NoteEditorContextProvider
key={controlPromptEditorRerenderKey}
value={data.text}
>
<>
<NodeResizer
nodeId={id}
nodeData={data}
icon={<Icon />}
minWidth={240}
maxWidth={640}
minHeight={88}
/>
<div className='shrink-0 h-2 opacity-50 rounded-t-md' style={{ background: THEME_MAP[theme].title }}></div>
{
data.selected && (
<div className='absolute -top-[41px] left-1/2 -translate-x-1/2'>
<NoteEditorToolbar
theme={theme}
onThemeChange={handleThemeChange}
onCopy={handleNodesCopy}
onDuplicate={handleNodesDuplicate}
onDelete={handleDeleteNode}
showAuthor={data.showAuthor}
onShowAuthorChange={handleShowAuthorChange}
/>
</div>
)
}
<div className='grow px-3 py-2.5 overflow-y-auto'>
<div className={cn(
data.selected && 'nodrag nopan nowheel cursor-text',
)}>
<NoteEditor
containerElement={ref.current}
placeholder={t('workflow.nodes.note.editor.placeholder') || ''}
onChange={handleEditorChange}
/>
</div>
</div>
{
data.showAuthor && (
<div className='p-3 pt-0 text-xs text-black/[0.32]'>
{data.author}
</div>
)
}
</>
</NoteEditorContextProvider>
</div>
)
}
export default memo(NoteNode)

View File

@ -0,0 +1,65 @@
'use client'
import {
createContext,
memo,
useRef,
} from 'react'
import { LexicalComposer } from '@lexical/react/LexicalComposer'
import { LinkNode } from '@lexical/link'
import {
ListItemNode,
ListNode,
} from '@lexical/list'
import { createNoteEditorStore } from './store'
import theme from './theme'
type NoteEditorStore = ReturnType<typeof createNoteEditorStore>
const NoteEditorContext = createContext<NoteEditorStore | null>(null)
type NoteEditorContextProviderProps = {
value: string
children: JSX.Element | string | (JSX.Element | string)[]
}
export const NoteEditorContextProvider = memo(({
value,
children,
}: NoteEditorContextProviderProps) => {
const storeRef = useRef<NoteEditorStore>()
if (!storeRef.current)
storeRef.current = createNoteEditorStore()
let initialValue = null
try {
initialValue = JSON.parse(value)
}
catch (e) {
}
const initialConfig = {
namespace: 'note-editor',
nodes: [
LinkNode,
ListNode,
ListItemNode,
],
editorState: !initialValue?.root.children.length ? null : JSON.stringify(initialValue),
onError: (error: Error) => {
throw error
},
theme,
}
return (
<NoteEditorContext.Provider value={storeRef.current}>
<LexicalComposer initialConfig={{ ...initialConfig }}>
{children}
</LexicalComposer>
</NoteEditorContext.Provider>
)
})
NoteEditorContextProvider.displayName = 'NoteEditorContextProvider'
export default NoteEditorContext

View File

@ -0,0 +1,62 @@
'use client'
import {
memo,
useCallback,
} from 'react'
import type { EditorState } from 'lexical'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
import { ClickableLinkPlugin } from '@lexical/react/LexicalClickableLinkPlugin'
import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin'
import { ListPlugin } from '@lexical/react/LexicalListPlugin'
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'
import LinkEditorPlugin from './plugins/link-editor-plugin'
import FormatDetectorPlugin from './plugins/format-detector-plugin'
// import TreeView from '@/app/components/base/prompt-editor/plugins/tree-view'
import Placeholder from '@/app/components/base/prompt-editor/plugins/placeholder'
type EditorProps = {
placeholder?: string
onChange?: (editorState: EditorState) => void
containerElement: HTMLDivElement | null
}
const Editor = ({
placeholder = 'write you note...',
onChange,
containerElement,
}: EditorProps) => {
const handleEditorChange = useCallback((editorState: EditorState) => {
onChange?.(editorState)
}, [onChange])
return (
<div className='relative'>
<RichTextPlugin
contentEditable={
<div>
<ContentEditable
spellCheck={false}
className='w-full h-full outline-none caret-primary-600'
placeholder={placeholder}
/>
</div>
}
placeholder={<Placeholder value={placeholder} compact />}
ErrorBoundary={LexicalErrorBoundary}
/>
<ClickableLinkPlugin disabled />
<LinkPlugin />
<ListPlugin />
<LinkEditorPlugin containerElement={containerElement} />
<FormatDetectorPlugin />
<HistoryPlugin />
<OnChangePlugin onChange={handleEditorChange} />
{/* <TreeView /> */}
</div>
)
}
export default memo(Editor)

View File

@ -0,0 +1,3 @@
export { NoteEditorContextProvider } from './context'
export { default as NoteEditor } from './editor'
export { default as NoteEditorToolbar } from './toolbar'

View File

@ -0,0 +1,78 @@
import {
useCallback,
useEffect,
} from 'react'
import {
$getSelection,
$isRangeSelection,
} from 'lexical'
import { mergeRegister } from '@lexical/utils'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import type { LinkNode } from '@lexical/link'
import { $isLinkNode } from '@lexical/link'
import { $isListItemNode } from '@lexical/list'
import { getSelectedNode } from '../../utils'
import { useNoteEditorStore } from '../../store'
export const useFormatDetector = () => {
const [editor] = useLexicalComposerContext()
const noteEditorStore = useNoteEditorStore()
const handleFormat = useCallback(() => {
editor.getEditorState().read(() => {
if (editor.isComposing())
return
const selection = $getSelection()
if ($isRangeSelection(selection)) {
const node = getSelectedNode(selection)
const {
setSelectedIsBold,
setSelectedIsItalic,
setSelectedIsStrikeThrough,
setSelectedLinkUrl,
setSelectedIsLink,
setSelectedIsBullet,
} = noteEditorStore.getState()
setSelectedIsBold(selection.hasFormat('bold'))
setSelectedIsItalic(selection.hasFormat('italic'))
setSelectedIsStrikeThrough(selection.hasFormat('strikethrough'))
const parent = node.getParent()
if ($isLinkNode(parent) || $isLinkNode(node)) {
const linkUrl = ($isLinkNode(parent) ? parent : node as LinkNode).getURL()
setSelectedLinkUrl(linkUrl)
setSelectedIsLink(true)
}
else {
setSelectedLinkUrl('')
setSelectedIsLink(false)
}
if ($isListItemNode(parent) || $isListItemNode(node))
setSelectedIsBullet(true)
else
setSelectedIsBullet(false)
}
})
}, [editor, noteEditorStore])
useEffect(() => {
document.addEventListener('selectionchange', handleFormat)
return () => {
document.removeEventListener('selectionchange', handleFormat)
}
}, [handleFormat])
useEffect(() => {
return mergeRegister(
editor.registerUpdateListener(() => {
handleFormat()
}),
)
}, [editor, handleFormat])
return {
handleFormat,
}
}

View File

@ -0,0 +1,9 @@
import { useFormatDetector } from './hooks'
const FormatDetectorPlugin = () => {
useFormatDetector()
return null
}
export default FormatDetectorPlugin

View File

@ -0,0 +1,152 @@
import {
memo,
useEffect,
useState,
} from 'react'
import { escape } from 'lodash-es'
import {
FloatingPortal,
flip,
offset,
shift,
useFloating,
} from '@floating-ui/react'
import { useTranslation } from 'react-i18next'
import { useClickAway } from 'ahooks'
import cn from 'classnames'
import { useStore } from '../../store'
import { useLink } from './hooks'
import Button from '@/app/components/base/button'
import {
Edit03,
LinkBroken01,
LinkExternal01,
} from '@/app/components/base/icons/src/vender/line/general'
type LinkEditorComponentProps = {
containerElement: HTMLDivElement | null
}
const LinkEditorComponent = ({
containerElement,
}: LinkEditorComponentProps) => {
const { t } = useTranslation()
const {
handleSaveLink,
handleUnlink,
} = useLink()
const selectedLinkUrl = useStore(s => s.selectedLinkUrl)
const linkAnchorElement = useStore(s => s.linkAnchorElement)
const linkOperatorShow = useStore(s => s.linkOperatorShow)
const setLinkAnchorElement = useStore(s => s.setLinkAnchorElement)
const setLinkOperatorShow = useStore(s => s.setLinkOperatorShow)
const [url, setUrl] = useState(selectedLinkUrl)
const { refs, floatingStyles, elements } = useFloating({
placement: 'top',
middleware: [
offset(4),
shift(),
flip(),
],
})
useClickAway(() => {
setLinkAnchorElement()
}, linkAnchorElement)
useEffect(() => {
setUrl(selectedLinkUrl)
}, [selectedLinkUrl])
useEffect(() => {
if (linkAnchorElement)
refs.setReference(linkAnchorElement)
}, [linkAnchorElement, refs])
return (
<>
{
elements.reference && (
<FloatingPortal root={containerElement}>
<div
className={cn(
'nodrag nopan inline-flex items-center w-max rounded-md border-[0.5px] border-black/5 bg-white z-10',
!linkOperatorShow && 'p-1 shadow-md',
linkOperatorShow && 'p-0.5 shadow-sm text-xs text-gray-500 font-medium',
)}
style={floatingStyles}
ref={refs.setFloating}
>
{
!linkOperatorShow && (
<>
<input
className='mr-0.5 p-1 w-[196px] h-6 rounded-sm text-[13px] appearance-none outline-none'
value={url}
onChange={e => setUrl(e.target.value)}
placeholder={t('workflow.nodes.note.editor.enterUrl') || ''}
autoFocus
/>
<Button
type='primary'
className={cn(
'py-0 px-2 h-6 text-xs',
!url && 'cursor-not-allowed',
)}
disabled={!url}
onClick={() => handleSaveLink(url)}
>
{t('common.operation.ok')}
</Button>
</>
)
}
{
linkOperatorShow && (
<>
<a
className='flex items-center px-2 h-6 rounded-md hover:bg-gray-50'
href={escape(url)}
target='_blank'
rel='noreferrer'
>
<LinkExternal01 className='mr-1 w-3 h-3' />
<div className='mr-1'>
{t('workflow.nodes.note.editor.openLink')}
</div>
<div
title={escape(url)}
className='text-primary-600 max-w-[140px] truncate'
>
{escape(url)}
</div>
</a>
<div className='mx-1 w-[1px] h-3.5 bg-gray-100'></div>
<div
className='flex items-center mr-0.5 px-2 h-6 rounded-md cursor-pointer hover:bg-gray-50'
onClick={(e) => {
e.stopPropagation()
setLinkOperatorShow(false)
}}
>
<Edit03 className='mr-1 w-3 h-3' />
{t('common.operation.edit')}
</div>
<div
className='flex items-center px-2 h-6 rounded-md cursor-pointer hover:bg-gray-50'
onClick={handleUnlink}
>
<LinkBroken01 className='mr-1 w-3 h-3' />
{t('workflow.nodes.note.editor.unlink')}
</div>
</>
)
}
</div>
</FloatingPortal>
)
}
</>
)
}
export default memo(LinkEditorComponent)

View File

@ -0,0 +1,115 @@
import {
useCallback,
useEffect,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
CLICK_COMMAND,
COMMAND_PRIORITY_LOW,
} from 'lexical'
import {
mergeRegister,
} from '@lexical/utils'
import {
TOGGLE_LINK_COMMAND,
} from '@lexical/link'
import { escape } from 'lodash-es'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useNoteEditorStore } from '../../store'
import { urlRegExp } from '../../utils'
import { useToastContext } from '@/app/components/base/toast'
export const useOpenLink = () => {
const [editor] = useLexicalComposerContext()
const noteEditorStore = useNoteEditorStore()
useEffect(() => {
return mergeRegister(
editor.registerUpdateListener(() => {
setTimeout(() => {
const {
selectedLinkUrl,
selectedIsLink,
setLinkAnchorElement,
setLinkOperatorShow,
} = noteEditorStore.getState()
if (selectedIsLink) {
setLinkAnchorElement(true)
if (selectedLinkUrl)
setLinkOperatorShow(true)
else
setLinkOperatorShow(false)
}
else {
setLinkAnchorElement()
setLinkOperatorShow(false)
}
})
}),
editor.registerCommand(
CLICK_COMMAND,
(payload) => {
setTimeout(() => {
const {
selectedLinkUrl,
selectedIsLink,
setLinkAnchorElement,
setLinkOperatorShow,
} = noteEditorStore.getState()
if (selectedIsLink) {
if ((payload.metaKey || payload.ctrlKey) && selectedLinkUrl) {
window.open(selectedLinkUrl, '_blank')
return true
}
setLinkAnchorElement(true)
if (selectedLinkUrl)
setLinkOperatorShow(true)
else
setLinkOperatorShow(false)
}
else {
setLinkAnchorElement()
setLinkOperatorShow(false)
}
})
return false
},
COMMAND_PRIORITY_LOW,
),
)
}, [editor, noteEditorStore])
}
export const useLink = () => {
const { t } = useTranslation()
const [editor] = useLexicalComposerContext()
const noteEditorStore = useNoteEditorStore()
const { notify } = useToastContext()
const handleSaveLink = useCallback((url: string) => {
if (url && !urlRegExp.test(url)) {
notify({ type: 'error', message: t('workflow.nodes.note.editor.invalidUrl') })
return
}
editor.dispatchCommand(TOGGLE_LINK_COMMAND, escape(url))
const { setLinkAnchorElement } = noteEditorStore.getState()
setLinkAnchorElement()
}, [editor, noteEditorStore, notify, t])
const handleUnlink = useCallback(() => {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null)
const { setLinkAnchorElement } = noteEditorStore.getState()
setLinkAnchorElement()
}, [editor, noteEditorStore])
return {
handleSaveLink,
handleUnlink,
}
}

View File

@ -0,0 +1,25 @@
import {
memo,
} from 'react'
import { useStore } from '../../store'
import { useOpenLink } from './hooks'
import LinkEditorComponent from './component'
type LinkEditorPluginProps = {
containerElement: HTMLDivElement | null
}
const LinkEditorPlugin = ({
containerElement,
}: LinkEditorPluginProps) => {
useOpenLink()
const linkAnchorElement = useStore(s => s.linkAnchorElement)
if (!linkAnchorElement)
return null
return (
<LinkEditorComponent containerElement={containerElement} />
)
}
export default memo(LinkEditorPlugin)

View File

@ -0,0 +1,72 @@
import { useContext } from 'react'
import {
useStore as useZustandStore,
} from 'zustand'
import { createStore } from 'zustand/vanilla'
import NoteEditorContext from './context'
type Shape = {
linkAnchorElement: HTMLElement | null
setLinkAnchorElement: (open?: boolean) => void
linkOperatorShow: boolean
setLinkOperatorShow: (linkOperatorShow: boolean) => void
selectedIsBold: boolean
setSelectedIsBold: (selectedIsBold: boolean) => void
selectedIsItalic: boolean
setSelectedIsItalic: (selectedIsItalic: boolean) => void
selectedIsStrikeThrough: boolean
setSelectedIsStrikeThrough: (selectedIsStrikeThrough: boolean) => void
selectedLinkUrl: string
setSelectedLinkUrl: (selectedLinkUrl: string) => void
selectedIsLink: boolean
setSelectedIsLink: (selectedIsLink: boolean) => void
selectedIsBullet: boolean
setSelectedIsBullet: (selectedIsBullet: boolean) => void
}
export const createNoteEditorStore = () => {
return createStore<Shape>(set => ({
linkAnchorElement: null,
setLinkAnchorElement: (open) => {
if (open) {
setTimeout(() => {
const nativeSelection = window.getSelection()
if (nativeSelection?.focusNode) {
const parent = nativeSelection.focusNode.parentElement
set(() => ({ linkAnchorElement: parent }))
}
})
}
else {
set(() => ({ linkAnchorElement: null }))
}
},
linkOperatorShow: false,
setLinkOperatorShow: linkOperatorShow => set(() => ({ linkOperatorShow })),
selectedIsBold: false,
setSelectedIsBold: selectedIsBold => set(() => ({ selectedIsBold })),
selectedIsItalic: false,
setSelectedIsItalic: selectedIsItalic => set(() => ({ selectedIsItalic })),
selectedIsStrikeThrough: false,
setSelectedIsStrikeThrough: selectedIsStrikeThrough => set(() => ({ selectedIsStrikeThrough })),
selectedLinkUrl: '',
setSelectedLinkUrl: selectedLinkUrl => set(() => ({ selectedLinkUrl })),
selectedIsLink: false,
setSelectedIsLink: selectedIsLink => set(() => ({ selectedIsLink })),
selectedIsBullet: false,
setSelectedIsBullet: selectedIsBullet => set(() => ({ selectedIsBullet })),
}))
}
export function useStore<T>(selector: (state: Shape) => T): T {
const store = useContext(NoteEditorContext)
if (!store)
throw new Error('Missing NoteEditorContext.Provider in the tree')
return useZustandStore(store, selector)
}
export const useNoteEditorStore = () => {
return useContext(NoteEditorContext)!
}

View File

@ -0,0 +1,17 @@
import type { EditorThemeClasses } from 'lexical'
import './theme.css'
const theme: EditorThemeClasses = {
paragraph: 'note-editor-theme_paragraph',
list: {
ul: 'note-editor-theme_list-ul',
listitem: 'note-editor-theme_list-li',
},
link: 'note-editor-theme_link',
text: {
strikethrough: 'note-editor-theme_text-strikethrough',
},
}
export default theme

View File

@ -0,0 +1,24 @@
.note-editor-theme_paragraph {
font-size: 12px;
}
.note-editor-theme_list-ul {
font-size: 12px;
margin: 0;
padding: 0;
list-style: disc;
}
.note-editor-theme_list-li {
margin-left: 18px;
margin-right: 8px;
}
.note-editor-theme_link {
text-decoration: underline;
cursor: pointer;
}
.note-editor-theme_text-strikethrough {
text-decoration: line-through;
}

View File

@ -0,0 +1,105 @@
import {
memo,
useState,
} from 'react'
import cn from 'classnames'
import { NoteTheme } from '../../types'
import { THEME_MAP } from '../../constants'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
export const COLOR_LIST = [
{
key: NoteTheme.blue,
inner: THEME_MAP[NoteTheme.blue].title,
outer: THEME_MAP[NoteTheme.blue].outer,
},
{
key: NoteTheme.cyan,
inner: THEME_MAP[NoteTheme.cyan].title,
outer: THEME_MAP[NoteTheme.cyan].outer,
},
{
key: NoteTheme.green,
inner: THEME_MAP[NoteTheme.green].title,
outer: THEME_MAP[NoteTheme.green].outer,
},
{
key: NoteTheme.yellow,
inner: THEME_MAP[NoteTheme.yellow].title,
outer: THEME_MAP[NoteTheme.yellow].outer,
},
{
key: NoteTheme.pink,
inner: THEME_MAP[NoteTheme.pink].title,
outer: THEME_MAP[NoteTheme.pink].outer,
},
{
key: NoteTheme.violet,
inner: THEME_MAP[NoteTheme.violet].title,
outer: THEME_MAP[NoteTheme.violet].outer,
},
]
export type ColorPickerProps = {
theme: NoteTheme
onThemeChange: (theme: NoteTheme) => void
}
const ColorPicker = ({
theme,
onThemeChange,
}: ColorPickerProps) => {
const [open, setOpen] = useState(false)
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='top'
offset={4}
>
<PortalToFollowElemTrigger onClick={() => setOpen(!open)}>
<div className={cn(
'flex items-center justify-center w-8 h-8 rounded-md cursor-pointer hover:bg-black/5',
open && 'bg-black/5',
)}>
<div
className='w-4 h-4 rounded-full border border-black/5'
style={{ backgroundColor: THEME_MAP[theme].title }}
></div>
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='grid grid-cols-3 grid-rows-2 gap-0.5 p-0.5 rounded-lg border-[0.5px] border-black/8 bg-white shadow-lg'>
{
COLOR_LIST.map(color => (
<div
key={color.key}
className='group relative flex items-center justify-center w-8 h-8 rounded-md cursor-pointer'
onClick={(e) => {
e.stopPropagation()
onThemeChange(color.key)
setOpen(false)
}}
>
<div
className='hidden group-hover:block absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-5 h-5 rounded-full border-[1.5px]'
style={{ borderColor: color.outer }}
></div>
<div
className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-4 h-4 rounded-full border border-black/5'
style={{ backgroundColor: color.inner }}
></div>
</div>
))
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default memo(ColorPicker)

View File

@ -0,0 +1,81 @@
import {
memo,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import { useStore } from '../store'
import { useCommand } from './hooks'
import { Link01 } from '@/app/components/base/icons/src/vender/line/general'
import {
Bold01,
Dotpoints01,
Italic01,
Strikethrough01,
} from '@/app/components/base/icons/src/vender/line/editor'
import TooltipPlus from '@/app/components/base/tooltip-plus'
type CommandProps = {
type: 'bold' | 'italic' | 'strikethrough' | 'link' | 'bullet'
}
const Command = ({
type,
}: CommandProps) => {
const { t } = useTranslation()
const selectedIsBold = useStore(s => s.selectedIsBold)
const selectedIsItalic = useStore(s => s.selectedIsItalic)
const selectedIsStrikeThrough = useStore(s => s.selectedIsStrikeThrough)
const selectedIsLink = useStore(s => s.selectedIsLink)
const selectedIsBullet = useStore(s => s.selectedIsBullet)
const { handleCommand } = useCommand()
const icon = useMemo(() => {
switch (type) {
case 'bold':
return <Bold01 className={cn('w-4 h-4', selectedIsBold && 'text-primary-600')} />
case 'italic':
return <Italic01 className={cn('w-4 h-4', selectedIsItalic && 'text-primary-600')} />
case 'strikethrough':
return <Strikethrough01 className={cn('w-4 h-4', selectedIsStrikeThrough && 'text-primary-600')} />
case 'link':
return <Link01 className={cn('w-4 h-4', selectedIsLink && 'text-primary-600')} />
case 'bullet':
return <Dotpoints01 className={cn('w-4 h-4', selectedIsBullet && 'text-primary-600')} />
}
}, [type, selectedIsBold, selectedIsItalic, selectedIsStrikeThrough, selectedIsLink, selectedIsBullet])
const tip = useMemo(() => {
switch (type) {
case 'bold':
return t('workflow.nodes.note.editor.bold')
case 'italic':
return t('workflow.nodes.note.editor.italic')
case 'strikethrough':
return t('workflow.nodes.note.editor.strikethrough')
case 'link':
return t('workflow.nodes.note.editor.link')
case 'bullet':
return t('workflow.nodes.note.editor.bulletList')
}
}, [type, t])
return (
<TooltipPlus popupContent={tip}>
<div
className={cn(
'flex items-center justify-center w-8 h-8 cursor-pointer rounded-md text-gray-500 hover:text-gray-800 hover:bg-black/5',
type === 'bold' && selectedIsBold && 'bg-primary-50',
type === 'italic' && selectedIsItalic && 'bg-primary-50',
type === 'strikethrough' && selectedIsStrikeThrough && 'bg-primary-50',
type === 'link' && selectedIsLink && 'bg-primary-50',
type === 'bullet' && selectedIsBullet && 'bg-primary-50',
)}
onClick={() => handleCommand(type)}
>
{icon}
</div>
</TooltipPlus>
)
}
export default memo(Command)

View File

@ -0,0 +1,7 @@
const Divider = () => {
return (
<div className='mx-1 w-[1px] h-3.5 bg-gray-200'></div>
)
}
export default Divider

View File

@ -0,0 +1,86 @@
import { memo } from 'react'
import cn from 'classnames'
import { useTranslation } from 'react-i18next'
import { useFontSize } from './hooks'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { TitleCase } from '@/app/components/base/icons/src/vender/line/editor'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import { Check } from '@/app/components/base/icons/src/vender/line/general'
const FontSizeSelector = () => {
const { t } = useTranslation()
const FONT_SIZE_LIST = [
{
key: '12px',
value: t('workflow.nodes.note.editor.small'),
},
{
key: '14px',
value: t('workflow.nodes.note.editor.medium'),
},
{
key: '16px',
value: t('workflow.nodes.note.editor.large'),
},
]
const {
fontSizeSelectorShow,
handleOpenFontSizeSelector,
fontSize,
handleFontSize,
} = useFontSize()
return (
<PortalToFollowElem
open={fontSizeSelectorShow}
onOpenChange={handleOpenFontSizeSelector}
placement='bottom-start'
offset={2}
>
<PortalToFollowElemTrigger onClick={() => handleOpenFontSizeSelector(!fontSizeSelectorShow)}>
<div className={cn(
'flex items-center pl-2 pr-1.5 h-8 rounded-md text-[13px] font-medium text-gray-700 cursor-pointer hover:bg-gray-50',
fontSizeSelectorShow && 'bg-gray-50',
)}>
<TitleCase className='mr-1 w-4 h-4' />
{FONT_SIZE_LIST.find(font => font.key === fontSize)?.value || t('workflow.nodes.note.editor.small')}
<ChevronDown className='ml-0.5 w-3 h-3' />
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='p-1 w-[120px] bg-white border-[0.5px] border-gray-200 rounded-md shadow-xl text-gray-700'>
{
FONT_SIZE_LIST.map(font => (
<div
key={font.key}
className='flex items-center justify-between pl-3 pr-2 h-8 rounded-md cursor-pointer hover:bg-gray-50'
onClick={(e) => {
e.stopPropagation()
handleFontSize(font.key)
handleOpenFontSizeSelector(false)
}}
>
<div
style={{ fontSize: font.key }}
>
{font.value}
</div>
{
fontSize === font.key && (
<Check className='w-4 h-4 text-primary-500' />
)
}
</div>
))
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default memo(FontSizeSelector)

View File

@ -0,0 +1,147 @@
import {
useCallback,
useEffect,
useState,
} from 'react'
import {
$createParagraphNode,
$getSelection,
$isRangeSelection,
$setSelection,
COMMAND_PRIORITY_CRITICAL,
FORMAT_TEXT_COMMAND,
SELECTION_CHANGE_COMMAND,
} from 'lexical'
import {
$getSelectionStyleValueForProperty,
$patchStyleText,
$setBlocksType,
} from '@lexical/selection'
import { INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list'
import { mergeRegister } from '@lexical/utils'
import {
$isLinkNode,
TOGGLE_LINK_COMMAND,
} from '@lexical/link'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useNoteEditorStore } from '../store'
import { getSelectedNode } from '../utils'
export const useCommand = () => {
const [editor] = useLexicalComposerContext()
const noteEditorStore = useNoteEditorStore()
const handleCommand = useCallback((type: string) => {
if (type === 'bold')
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold')
if (type === 'italic')
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic')
if (type === 'strikethrough')
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough')
if (type === 'link') {
editor.update(() => {
const selection = $getSelection()
if ($isRangeSelection(selection)) {
const node = getSelectedNode(selection)
const parent = node.getParent()
const { setLinkAnchorElement } = noteEditorStore.getState()
if ($isLinkNode(parent) || $isLinkNode(node)) {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null)
setLinkAnchorElement()
}
else {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, '')
setLinkAnchorElement(true)
}
}
})
}
if (type === 'bullet') {
const { selectedIsBullet } = noteEditorStore.getState()
if (selectedIsBullet) {
editor.update(() => {
const selection = $getSelection()
if ($isRangeSelection(selection))
$setBlocksType(selection, () => $createParagraphNode())
})
}
else {
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)
}
}
}, [editor, noteEditorStore])
return {
handleCommand,
}
}
export const useFontSize = () => {
const [editor] = useLexicalComposerContext()
const [fontSize, setFontSize] = useState('12px')
const [fontSizeSelectorShow, setFontSizeSelectorShow] = useState(false)
const handleFontSize = useCallback((fontSize: string) => {
editor.update(() => {
const selection = $getSelection()
if ($isRangeSelection(selection))
$patchStyleText(selection, { 'font-size': fontSize })
})
}, [editor])
const handleOpenFontSizeSelector = useCallback((newFontSizeSelectorShow: boolean) => {
if (newFontSizeSelectorShow) {
editor.update(() => {
const selection = $getSelection()
if ($isRangeSelection(selection))
$setSelection(selection.clone())
})
}
setFontSizeSelectorShow(newFontSizeSelectorShow)
}, [editor])
useEffect(() => {
return mergeRegister(
editor.registerUpdateListener(() => {
editor.getEditorState().read(() => {
const selection = $getSelection()
if ($isRangeSelection(selection)) {
const fontSize = $getSelectionStyleValueForProperty(selection, 'font-size', '12px')
setFontSize(fontSize)
}
})
}),
editor.registerCommand(
SELECTION_CHANGE_COMMAND,
() => {
const selection = $getSelection()
if ($isRangeSelection(selection)) {
const fontSize = $getSelectionStyleValueForProperty(selection, 'font-size', '12px')
setFontSize(fontSize)
}
return false
},
COMMAND_PRIORITY_CRITICAL,
),
)
}, [editor])
return {
fontSize,
handleFontSize,
fontSizeSelectorShow,
handleOpenFontSizeSelector,
}
}

View File

@ -0,0 +1,48 @@
import { memo } from 'react'
import Divider from './divider'
import type { ColorPickerProps } from './color-picker'
import ColorPicker from './color-picker'
import FontSizeSelector from './font-size-selector'
import Command from './command'
import type { OperatorProps } from './operator'
import Operator from './operator'
type ToolbarProps = ColorPickerProps & OperatorProps
const Toolbar = ({
theme,
onThemeChange,
onCopy,
onDuplicate,
onDelete,
showAuthor,
onShowAuthorChange,
}: ToolbarProps) => {
return (
<div className='inline-flex items-center p-0.5 bg-white rounded-lg border-[0.5px] border-black/5 shadow-sm'>
<ColorPicker
theme={theme}
onThemeChange={onThemeChange}
/>
<Divider />
<FontSizeSelector />
<Divider />
<div className='flex items-center space-x-0.5'>
<Command type='bold' />
<Command type='italic' />
<Command type='strikethrough' />
<Command type='link' />
<Command type='bullet' />
</div>
<Divider />
<Operator
onCopy={onCopy}
onDuplicate={onDuplicate}
onDelete={onDelete}
showAuthor={showAuthor}
onShowAuthorChange={onShowAuthorChange}
/>
</div>
)
}
export default memo(Toolbar)

View File

@ -0,0 +1,107 @@
import {
memo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import ShortcutsName from '@/app/components/workflow/shortcuts-name'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { DotsHorizontal } from '@/app/components/base/icons/src/vender/line/general'
import Switch from '@/app/components/base/switch'
export type OperatorProps = {
onCopy: () => void
onDuplicate: () => void
onDelete: () => void
showAuthor: boolean
onShowAuthorChange: (showAuthor: boolean) => void
}
const Operator = ({
onCopy,
onDelete,
onDuplicate,
showAuthor,
onShowAuthorChange,
}: OperatorProps) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
offset={4}
>
<PortalToFollowElemTrigger onClick={() => setOpen(!open)}>
<div
className={cn(
'flex items-center justify-center w-8 h-8 cursor-pointer rounded-lg hover:bg-black/5',
open && 'bg-black/5',
)}
>
<DotsHorizontal className='w-4 h-4 text-gray-500' />
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='min-w-[192px] bg-white rounded-md border-[0.5px] border-gray-200 shadow-xl'>
<div className='p-1'>
<div
className='flex items-center justify-between px-3 h-8 cursor-pointer rounded-md text-sm text-gray-700 hover:bg-black/5'
onClick={() => {
onCopy()
setOpen(false)
}}
>
{t('workflow.common.copy')}
<ShortcutsName keys={['ctrl', 'c']} />
</div>
<div
className='flex items-center justify-between px-3 h-8 cursor-pointer rounded-md text-sm text-gray-700 hover:bg-black/5'
onClick={() => {
onDuplicate()
setOpen(false)
}}
>
{t('workflow.common.duplicate')}
<ShortcutsName keys={['ctrl', 'd']} />
</div>
</div>
<div className='h-[1px] bg-gray-100'></div>
<div className='p-1'>
<div
className='flex items-center justify-between px-3 h-8 cursor-pointer rounded-md text-sm text-gray-700 hover:bg-black/5'
onClick={e => e.stopPropagation()}
>
<div>{t('workflow.nodes.note.editor.showAuthor')}</div>
<Switch
size='l'
defaultValue={showAuthor}
onChange={onShowAuthorChange}
/>
</div>
</div>
<div className='h-[1px] bg-gray-100'></div>
<div className='p-1'>
<div
className='flex items-center justify-between px-3 h-8 cursor-pointer rounded-md text-sm text-gray-700 hover:text-[#D92D20] hover:bg-[#FEF3F2]'
onClick={() => {
onDelete()
setOpen(false)
}}
>
{t('common.operation.delete')}
<ShortcutsName keys={['del']} />
</div>
</div>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default memo(Operator)

View File

@ -0,0 +1,21 @@
import { $isAtNodeEnd } from '@lexical/selection'
import type { ElementNode, RangeSelection, TextNode } from 'lexical'
export function getSelectedNode(
selection: RangeSelection,
): TextNode | ElementNode {
const anchor = selection.anchor
const focus = selection.focus
const anchorNode = selection.anchor.getNode()
const focusNode = selection.focus.getNode()
if (anchorNode === focusNode)
return anchorNode
const isBackward = selection.isBackward()
if (isBackward)
return $isAtNodeEnd(focus) ? anchorNode : focusNode
else
return $isAtNodeEnd(anchor) ? anchorNode : focusNode
}
export const urlRegExp = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/

View File

@ -0,0 +1,17 @@
import type { CommonNodeType } from '../types'
export enum NoteTheme {
blue = 'blue',
cyan = 'cyan',
green = 'green',
yellow = 'yellow',
pink = 'pink',
violet = 'violet',
}
export type NoteNodeType = CommonNodeType & {
text: string
theme: NoteTheme
author: string
showAuthor: boolean
}

View File

@ -1,4 +1,8 @@
import { memo, useCallback } from 'react'
import type { MouseEvent } from 'react'
import {
memo,
useCallback,
} from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import { useKeyPress } from 'ahooks'
@ -11,6 +15,7 @@ import { isEventTargetInputArea } from '../utils'
import { useStore } from '../store'
import AddBlock from './add-block'
import TipPopup from './tip-popup'
import { useOperator } from './hooks'
import {
Cursor02C,
Hand02,
@ -20,12 +25,14 @@ import {
Hand02 as Hand02Solid,
} from '@/app/components/base/icons/src/vender/solid/editor'
import { OrganizeGrid } from '@/app/components/base/icons/src/vender/line/layout'
import { StickerSquare } from '@/app/components/base/icons/src/vender/line/files'
const Control = () => {
const { t } = useTranslation()
const controlMode = useStore(s => s.controlMode)
const setControlMode = useStore(s => s.setControlMode)
const { handleLayout } = useWorkflow()
const { handleAddNote } = useOperator()
const {
nodesReadOnly,
getNodesReadOnly,
@ -75,9 +82,28 @@ const Control = () => {
handleLayout()
}
const addNote = (e: MouseEvent<HTMLDivElement>) => {
if (getNodesReadOnly())
return
e.stopPropagation()
handleAddNote()
}
return (
<div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500'>
<AddBlock />
<TipPopup title={t('workflow.nodes.note.addNote')}>
<div
className={cn(
'flex items-center justify-center ml-[1px] w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
)}
onClick={addNote}
>
<StickerSquare />
</div>
</TipPopup>
<div className='mx-[3px] w-[1px] h-3.5 bg-gray-200'></div>
<TipPopup title={t('workflow.common.pointerMode')}>
<div

View File

@ -0,0 +1,41 @@
import { useCallback } from 'react'
import { generateNewNode } from '../utils'
import { useWorkflowStore } from '../store'
import type { NoteNodeType } from '../note-node/types'
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
import { NoteTheme } from '../note-node/types'
import { useAppContext } from '@/context/app-context'
export const useOperator = () => {
const workflowStore = useWorkflowStore()
const { userProfile } = useAppContext()
const handleAddNote = useCallback(() => {
const newNode = generateNewNode({
type: CUSTOM_NOTE_NODE,
data: {
title: '',
desc: '',
type: '' as any,
text: '',
theme: NoteTheme.blue,
author: userProfile?.name || '',
showAuthor: true,
width: 240,
height: 88,
_isCandidate: true,
} as NoteNodeType,
position: {
x: 0,
y: 0,
},
})
workflowStore.setState({
candidateNode: newNode,
})
}, [workflowStore, userProfile])
return {
handleAddNote,
}
}

View File

@ -13,6 +13,7 @@ import {
useWorkflowStartRun,
} from './hooks'
import AddBlock from './operator/add-block'
import { useOperator } from './operator/hooks'
import { exportAppConfig } from '@/service/apps'
import { useToastContext } from '@/app/components/base/toast'
import { useStore as useAppStore } from '@/app/components/app/store'
@ -27,6 +28,7 @@ const PanelContextmenu = () => {
const { handleNodesPaste } = useNodesInteractions()
const { handlePaneContextmenuCancel } = usePanelInteractions()
const { handleStartWorkflowRun } = useWorkflowStartRun()
const { handleAddNote } = useOperator()
useClickAway(() => {
handlePaneContextmenuCancel()
@ -78,6 +80,16 @@ const PanelContextmenu = () => {
crossAxis: -4,
}}
/>
<div
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
onClick={(e) => {
e.stopPropagation()
handleAddNote()
handlePaneContextmenuCancel()
}}
>
{t('workflow.nodes.note.addNote')}
</div>
<div
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
onClick={() => {

View File

@ -17,6 +17,7 @@ import type {
} from './types'
import { BlockEnum } from './types'
import {
CUSTOM_NODE,
ITERATION_NODE_Z_INDEX,
NODE_WIDTH_X_OFFSET,
START_INITIAL_POSITION,
@ -105,7 +106,8 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
}, {} as Record<string, string[]>)
return nodes.map((node) => {
node.type = 'custom'
if (!node.type)
node.type = CUSTOM_NODE
const connectedEdges = getConnectedEdges([node], edges)
node.data._connectedSourceHandleIds = connectedEdges.filter(edge => edge.source === node.id).map(edge => edge.sourceHandle || 'source')
@ -189,7 +191,7 @@ export const initialEdges = (originEdges: Edge[], originNodes: Node[]) => {
export const getLayoutByDagre = (originNodes: Node[], originEdges: Edge[]) => {
const dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
const nodes = cloneDeep(originNodes).filter(node => !node.parentId)
const nodes = cloneDeep(originNodes).filter(node => !node.parentId && node.type === CUSTOM_NODE)
const edges = cloneDeep(originEdges).filter(edge => !edge.data?.isInIteration)
dagreGraph.setGraph({
rankdir: 'LR',
@ -280,10 +282,10 @@ export const getNodesConnectedSourceOrTargetHandleIdsMap = (changes: ConnectedSo
return nodesConnectedSourceOrTargetHandleIdsMap
}
export const generateNewNode = ({ data, position, id, zIndex, ...rest }: Omit<Node, 'id'> & { id?: string }) => {
export const generateNewNode = ({ data, position, id, zIndex, type, ...rest }: Omit<Node, 'id'> & { id?: string }) => {
return {
id: id || `${Date.now()}`,
type: 'custom',
type: type || CUSTOM_NODE,
data,
position,
targetPosition: Position.Left,

View File

@ -412,6 +412,25 @@ const translation = {
iteration_other: '{{count}} Iterations',
currentIteration: 'Current Iteration',
},
note: {
addNote: 'Add Note',
editor: {
placeholder: 'Write your note...',
small: 'Small',
medium: 'Medium',
large: 'Large',
bold: 'Bold',
italic: 'Italic',
strikethrough: 'Strikethrough',
link: 'Link',
openLink: 'Open',
unlink: 'Unlink',
enterUrl: 'Enter URL...',
invalidUrl: 'Invalid URL',
bulletList: 'Bullet List',
showAuthor: 'Show Author',
},
},
},
tracing: {
stopBy: 'Stop by {{user}}',

View File

@ -412,6 +412,25 @@ const translation = {
iteration_other: '{{count}}个迭代',
currentIteration: '当前迭代',
},
note: {
addNote: '添加注释',
editor: {
placeholder: '输入注释...',
small: '小',
medium: '中',
large: '大',
bold: '加粗',
italic: '斜体',
strikethrough: '删除线',
link: '链接',
openLink: '打开',
unlink: '取消链接',
enterUrl: '输入链接...',
invalidUrl: '无效的链接',
bulletList: '列表',
showAuthor: '显示作者',
},
},
},
tracing: {
stopBy: '由{{user}}终止',

View File

@ -23,7 +23,7 @@
"@headlessui/react": "^1.7.13",
"@heroicons/react": "^2.0.16",
"@hookform/resolvers": "^3.3.4",
"@lexical/react": "^0.12.2",
"@lexical/react": "^0.16.0",
"@mdx-js/loader": "^2.3.0",
"@mdx-js/react": "^2.3.0",
"@monaco-editor/react": "^4.6.0",
@ -47,7 +47,7 @@
"js-cookie": "^3.0.1",
"katex": "^0.16.10",
"lamejs": "^1.2.1",
"lexical": "^0.12.2",
"lexical": "^0.16.0",
"lodash-es": "^4.17.21",
"mermaid": "10.4.0",
"negotiator": "^0.6.3",

View File

@ -414,159 +414,206 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
"@lexical/clipboard@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.12.2.tgz"
integrity sha512-RldmfZquuJJJCJ5WquCyoJ1/eZ+AnNgdksqvd+G+Yn/GyJl/+O3dnHM0QVaDSPvh/PynLFcCtz/57ySLo2kQxQ==
"@lexical/clipboard@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/clipboard/-/clipboard-0.16.0.tgz#3ae0d87a56bd3518de077e45b0c1bbba2f356193"
integrity sha512-eYMJ6jCXpWBVC05Mu9HLMysrBbfi++xFfsm+Yo7A6kYGrqYUhpXqjJkYnw1xdZYL3bV73Oe4ByVJuq42GU+Mqw==
dependencies:
"@lexical/html" "0.12.2"
"@lexical/list" "0.12.2"
"@lexical/selection" "0.12.2"
"@lexical/utils" "0.12.2"
"@lexical/html" "0.16.0"
"@lexical/list" "0.16.0"
"@lexical/selection" "0.16.0"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/code@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/code/-/code-0.12.2.tgz"
integrity sha512-w2JeJdnMUtYnC/Fx78sL3iJBt9Ug8pFSDOcI9ay/BkMQFQV8oqq1iyuLLBBJSG4FAM8b2DXrVdGklRQ+jTfTVw==
"@lexical/code@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/code/-/code-0.16.0.tgz#225030342e3c361e5541c750033323007a947880"
integrity sha512-1EKCBSFV745UI2zn5v75sKcvVdmd+y2JtZhw8CItiQkRnBLv4l4d/RZYy+cKOuXJGsoBrKtxXn5sl7HebwQbPw==
dependencies:
"@lexical/utils" "0.12.2"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
prismjs "^1.27.0"
"@lexical/dragon@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.12.2.tgz"
integrity sha512-Mt8NLzTOt+VgQtc2DKDbHBwKeRlvKqbLqRIMYUVk60gol+YV7NpVBsP1PAMuYYjrTQLhlckBSC32H1SUHZRavA==
"@lexical/hashtag@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.12.2.tgz"
integrity sha512-2vYzIu5Ldf+eYdUrNA2m80c3N3MF3vJ0fIJzpl5QyX8OdViggEWl1bh+lKtw1Ju0H0CUyDIXdDLZ2apW3WDkTA==
"@lexical/devtools-core@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/devtools-core/-/devtools-core-0.16.0.tgz#326c8e2995ce6e6e9e1fc4654ee2affbecdbd46d"
integrity sha512-Jt8p0J0UoMHf3UMh3VdyrXbLLwpEZuMqihTmbPRpwo+YQ6NGQU35QgwY2K0DpPAThpxL/Cm7uaFqGOy8Kjrhqw==
dependencies:
"@lexical/utils" "0.12.2"
"@lexical/html" "0.16.0"
"@lexical/link" "0.16.0"
"@lexical/mark" "0.16.0"
"@lexical/table" "0.16.0"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/history@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/history/-/history-0.12.2.tgz"
integrity sha512-PM/EDjnUyBPMWh1UiYb7T+FLbvTk14HwUWLXvZxn72S6Kj8ExH/PfLbWZWLCFL8RfzvbP407VwfSN8S0bF5H6g==
"@lexical/dragon@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/dragon/-/dragon-0.16.0.tgz#de083903701af2bb5264309b565d613c3eec06a0"
integrity sha512-Yr29SFZzOPs+S6UrEZaXnnso1fJGVfZOXVJQZbyzlspqJpSHXVH7InOXYHWN6JSWQ8Hs/vU3ksJXwqz+0TCp2g==
dependencies:
"@lexical/utils" "0.12.2"
lexical "0.16.0"
"@lexical/html@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/html/-/html-0.12.2.tgz"
integrity sha512-LWUO6OKhDtDZa9X1spHAqzsp+4EF01exis4cz5H9y2sHi7EofogXnRCadZ+fa07NVwPVTZWsStkk5qdSe/NEzg==
"@lexical/hashtag@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/hashtag/-/hashtag-0.16.0.tgz#ea0187060a114678753adaf0a15aad59d4f49a71"
integrity sha512-2EdAvxYVYqb0nv6vgxCRgE8ip7yez5p0y0oeUyxmdbcfZdA+Jl90gYH3VdevmZ5Bk3wE0/fIqiLD+Bb5smqjCQ==
dependencies:
"@lexical/selection" "0.12.2"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/link@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/link/-/link-0.12.2.tgz"
integrity sha512-etOIONa7uyRDmwg8GN52kDlf8thD2Zk1LOFLeocHWz1V8fe3i2unGUek5s/rNPkc6ynpPpNsHdN1VEghOLCCmw==
"@lexical/history@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/history/-/history-0.16.0.tgz#f83f2e331957208c5c8186d98f2f84681d936cec"
integrity sha512-xwFxgDZGviyGEqHmgt6A6gPhsyU/yzlKRk9TBUVByba3khuTknlJ1a80H5jb+OYcrpiElml7iVuGYt+oC7atCA==
dependencies:
"@lexical/utils" "0.12.2"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/list@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/list/-/list-0.12.2.tgz"
integrity sha512-3CyWtYQC+IlK4cK/oiD8Uz1gSXD8UcKGOF2vVsDXkMU06O6zvHNmHZOnVJqA0JVNgZAoR9dMR1fi2xd4iuCAiw==
"@lexical/html@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/html/-/html-0.16.0.tgz#98477ed0dee4c7d910608f4e4de3fbd5eeecdffe"
integrity sha512-okxn3q/1qkUpCZNEFRI39XeJj4YRjb6prm3WqZgP4d39DI1W24feeTZJjYRCW+dc3NInwFaolU3pNA2MGkjRtg==
dependencies:
"@lexical/utils" "0.12.2"
"@lexical/selection" "0.16.0"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/mark@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/mark/-/mark-0.12.2.tgz"
integrity sha512-ub+37PDfmThsqAWipRTrwqpgE+83ckqJ5C3mKQUBZvhZfVZW1rEUXZnKjFh2Q3eZK6iT7zVgoVJWJS9ZgEEyag==
"@lexical/link@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/link/-/link-0.16.0.tgz#f137ab3071206ed3c3a8b8a302ed66b084399ed1"
integrity sha512-ppvJSh/XGqlzbeymOiwcXJcUcrqgQqTK2QXTBAZq7JThtb0WsJxYd2CSLSN+Ycu23prnwqOqILcU0+34+gAVFw==
dependencies:
"@lexical/utils" "0.12.2"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/markdown@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.12.2.tgz"
integrity sha512-F2jTFtBp7Q+yoA11BeUOEcxhROzW+HUhUGdsn20pSLhuxsWRj3oUuryWFeNKFofpzTCVoqU6dwpaMNMI2mL/sQ==
"@lexical/list@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/list/-/list-0.16.0.tgz#ed97733633492e89c68ad51a1d455b63ce5aa1c0"
integrity sha512-nBx/DMM7nCgnOzo1JyNnVaIrk/Xi5wIPNi8jixrEV6w9Om2K6dHutn/79Xzp2dQlNGSLHEDjky6N2RyFgmXh0g==
dependencies:
"@lexical/code" "0.12.2"
"@lexical/link" "0.12.2"
"@lexical/list" "0.12.2"
"@lexical/rich-text" "0.12.2"
"@lexical/text" "0.12.2"
"@lexical/utils" "0.12.2"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/offset@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/offset/-/offset-0.12.2.tgz"
integrity sha512-rZLZXfOBmpmM8A2UZsX3cr/CQYw5F/ou67AbaKI0WImb5sjnIgICZqzu9VFUnkKlVNUurEpplV3UG3D1YYh1OQ==
"@lexical/overflow@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.12.2.tgz"
integrity sha512-UgE5j3ukO6qRFRpH4T7m/DvnodE9nCtImD7QinyGdsTa0hi5xlRnl0FUo605vH+vz7xEsUNAGwQXYPX9Sc/vig==
"@lexical/plain-text@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.12.2.tgz"
integrity sha512-Lcg6+ngRnX70//kz34azYhID3bvW66HSHCfu5UPhCXT+vQ/Jkd/InhRKajBwWXpaJxMM1huoi3sjzVDb3luNtw==
"@lexical/react@^0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/react/-/react-0.12.2.tgz"
integrity sha512-ZBUvf5xmhiYWBw8pPrhYmLAEwFWrbF/cd15y76TUKD9l/2zDwwPs6nJQxBzfz3ei65r2/nnavLDV8W3QfvxfUA==
"@lexical/mark@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/mark/-/mark-0.16.0.tgz#e87d92845c8bd231ef47106c5d44e7e10d2a3934"
integrity sha512-WMR4nqygSgIQ6Vdr5WAzohxBGjH+m44dBNTbWTGZGVlRvPzvBT6tieCoxFqpceIq/ko67HGTCNoFj2cMKVwgIA==
dependencies:
"@lexical/clipboard" "0.12.2"
"@lexical/code" "0.12.2"
"@lexical/dragon" "0.12.2"
"@lexical/hashtag" "0.12.2"
"@lexical/history" "0.12.2"
"@lexical/link" "0.12.2"
"@lexical/list" "0.12.2"
"@lexical/mark" "0.12.2"
"@lexical/markdown" "0.12.2"
"@lexical/overflow" "0.12.2"
"@lexical/plain-text" "0.12.2"
"@lexical/rich-text" "0.12.2"
"@lexical/selection" "0.12.2"
"@lexical/table" "0.12.2"
"@lexical/text" "0.12.2"
"@lexical/utils" "0.12.2"
"@lexical/yjs" "0.12.2"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/markdown@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/markdown/-/markdown-0.16.0.tgz#fd2d2759d9d5554d9899c3e1fb30a868bfa162a2"
integrity sha512-7HQLFrBbpY68mcq4A6C1qIGmjgA+fAByditi2WRe7tD2eoIKb/B5baQAnDKis0J+m5kTaCBmdlT6csSzyOPzeQ==
dependencies:
"@lexical/code" "0.16.0"
"@lexical/link" "0.16.0"
"@lexical/list" "0.16.0"
"@lexical/rich-text" "0.16.0"
"@lexical/text" "0.16.0"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/offset@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/offset/-/offset-0.16.0.tgz#bb3bc695ed403db0795f095330c68cdc5cbbec4b"
integrity sha512-4TqPEC2qA7sgO8Tm65nOWnhJ8dkl22oeuGv9sUB+nhaiRZnw3R45mDelg23r56CWE8itZnvueE7TKvV+F3OXtQ==
dependencies:
lexical "0.16.0"
"@lexical/overflow@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/overflow/-/overflow-0.16.0.tgz#31b791f7f7005ea4b160f3ae8083a2b3de05cfdc"
integrity sha512-a7gtIRxleEuMN9dj2yO4CdezBBfIr9Mq+m7G5z62+xy7VL7cfMfF+xWjy3EmDYDXS4vOQgAXAUgO4oKz2AKGhQ==
dependencies:
lexical "0.16.0"
"@lexical/plain-text@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/plain-text/-/plain-text-0.16.0.tgz#b903bfb59fb6629ded24194e1bef451df3383393"
integrity sha512-BK7/GSOZUHRJTbNPkpb9a/xN9z+FBCdunTsZhnOY8pQ7IKws3kuMO2Tk1zXfTd882ZNAxFdDKNdLYDSeufrKpw==
dependencies:
"@lexical/clipboard" "0.16.0"
"@lexical/selection" "0.16.0"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/react@^0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/react/-/react-0.16.0.tgz#0bd3ae63ceb5ad8b77e8c0e8ba7df1a0369462f0"
integrity sha512-WKFQbI0/m1YkLjL5t90YLJwjGcl5QRe6mkfm3ljQuL7Ioj3F92ZN/J2gHFVJ9iC8/lJs6Zzw6oFjiP8hQxJf9Q==
dependencies:
"@lexical/clipboard" "0.16.0"
"@lexical/code" "0.16.0"
"@lexical/devtools-core" "0.16.0"
"@lexical/dragon" "0.16.0"
"@lexical/hashtag" "0.16.0"
"@lexical/history" "0.16.0"
"@lexical/link" "0.16.0"
"@lexical/list" "0.16.0"
"@lexical/mark" "0.16.0"
"@lexical/markdown" "0.16.0"
"@lexical/overflow" "0.16.0"
"@lexical/plain-text" "0.16.0"
"@lexical/rich-text" "0.16.0"
"@lexical/selection" "0.16.0"
"@lexical/table" "0.16.0"
"@lexical/text" "0.16.0"
"@lexical/utils" "0.16.0"
"@lexical/yjs" "0.16.0"
lexical "0.16.0"
react-error-boundary "^3.1.4"
"@lexical/rich-text@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.12.2.tgz"
integrity sha512-igsEuv7CwBOAj5c8jeE41cnx6zkhI/Bkbu4W7shT6S6lNA/3cnyZpAMlgixwyK5RoqjGRCT+IJK5l6yBxQfNkw==
"@lexical/selection@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/selection/-/selection-0.12.2.tgz"
integrity sha512-h+g3oOnihHKIyLTyG6uLCEVR/DmUEVdCcZO1iAoGsuW7nwWiWNPWj6oZ3Cw5J1Mk5u62DHnkkVDQsVSZbAwmtg==
"@lexical/table@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/table/-/table-0.12.2.tgz"
integrity sha512-tiAmTq6RKHDVER9v589Ajm9/RL+WTF1WschrH6HHVCtil6cfJfTJeJ+MF45+XEzB9fkqy2LfrScAfWxqLjVePA==
"@lexical/rich-text@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/rich-text/-/rich-text-0.16.0.tgz#5b9ea6ceb1ea034fa7adf1770bd7fa6af1571d1d"
integrity sha512-AGTD6yJZ+kj2TNah1r7/6vyufs6fZANeSvv9x5eG+WjV4uyUJYkd1qR8C5gFZHdkyr+bhAcsAXvS039VzAxRrQ==
dependencies:
"@lexical/utils" "0.12.2"
"@lexical/clipboard" "0.16.0"
"@lexical/selection" "0.16.0"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/text@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/text/-/text-0.12.2.tgz"
integrity sha512-HyuIGuQvVi5djJKKBf+jYEBjK+0Eo9cKHf6WS7dlFozuCZvcCQEJkFy2yceWOwIVk+f2kptVQ5uO7aiZHExH2A==
"@lexical/utils@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/utils/-/utils-0.12.2.tgz"
integrity sha512-xW4y4l2Yd37+qLwkBvBGyzsKCA9wnh1ljphBJeR2vreT193i2gaIwuku2ZKlER14VHw4192qNJF7vUoAEmwurQ==
"@lexical/selection@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/selection/-/selection-0.16.0.tgz#8e09edb1e555e79c646a0105beab58ac21fc7158"
integrity sha512-trT9gQVJ2j6AwAe7tHJ30SRuxCpV6yR9LFtggxphHsXSvJYnoHC0CXh1TF2jHl8Gd5OsdWseexGLBE4Y0V3gwQ==
dependencies:
"@lexical/list" "0.12.2"
"@lexical/selection" "0.12.2"
"@lexical/table" "0.12.2"
lexical "0.16.0"
"@lexical/yjs@0.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.12.2.tgz"
integrity sha512-OPJhkJD1Mp9W80mfLzASTB3OFWFMzJteUYA+eSyDgiX9zNi1VGxAqmIITTkDvnCMa+qvw4EfhGeGezpjx6Og4A==
"@lexical/table@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/table/-/table-0.16.0.tgz#68592afbb0f9c0d9bf42bebaae626b8129fc470d"
integrity sha512-A66K779kxdr0yH2RwT2itsMnkzyFLFNPXyiWGLobCH8ON4QPuBouZvjbRHBe8Pe64yJ0c1bRDxSbTqUi9Wt3Gg==
dependencies:
"@lexical/offset" "0.12.2"
"@lexical/utils" "0.16.0"
lexical "0.16.0"
"@lexical/text@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/text/-/text-0.16.0.tgz#fc4789591f8aaa4a33bc1814280bc8725fd036a9"
integrity sha512-9ilaOhuNIIGHKC8g8j3K/mEvJ09af9B6RKbm3GNoRcf/WNHD4dEFWNTEvgo/3zCzAS8EUBI6UINmfQQWlMjdIQ==
dependencies:
lexical "0.16.0"
"@lexical/utils@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/utils/-/utils-0.16.0.tgz#6ad5785c53347aed5b39c980240c09b21c4a7469"
integrity sha512-GWmFEmd7o3GHqJBaEwzuZQbfTNI3Gg8ReGuHMHABgrkhZ8j2NggoRBlxsQLG0f7BewfTMVwbye22yBPq78775w==
dependencies:
"@lexical/list" "0.16.0"
"@lexical/selection" "0.16.0"
"@lexical/table" "0.16.0"
lexical "0.16.0"
"@lexical/yjs@0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@lexical/yjs/-/yjs-0.16.0.tgz#e27bec25c12e90f7768b980da08f2d2d9919d25b"
integrity sha512-YIJr87DfAXTwoVHDjR7cci//hr4r/a61Nn95eo2JNwbTqQo65Gp8rwJivqVxNfvKZmRdwHTKgvdEDoBmI/tGog==
dependencies:
"@lexical/offset" "0.16.0"
lexical "0.16.0"
"@mdx-js/loader@^2.3.0":
version "2.3.0"
@ -4287,10 +4334,10 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
lexical@^0.12.2:
version "0.12.2"
resolved "https://registry.npmjs.org/lexical/-/lexical-0.12.2.tgz"
integrity sha512-Kxavd+ETjxtVwG/hvPd6WZfXD44sLOKe9Vlkwxy7lBQ1qZArS+rZfs+u5iXwXe6tX9f2PIM0u3RHsrCEDDE0fw==
lexical@0.16.0, lexical@^0.16.0:
version "0.16.0"
resolved "https://registry.yarnpkg.com/lexical/-/lexical-0.16.0.tgz#0515d4003cbfba5a5e0e3e50f32f65076a6b89e2"
integrity sha512-Skn45Qhriazq4fpAtwnAB11U//GKc4vjzx54xsV3TkDLDvWpbL4Z9TNRwRoN3g7w8AkWnqjeOSODKkrjgfRSrg==
lilconfig@2.1.0, lilconfig@^2.0.5, lilconfig@^2.1.0:
version "2.1.0"