Bladeren bron

feat: workflow add note node (#5164)

tags/0.6.11
zxhlyh 1 jaar geleden
bovenliggende
commit
c28d709d7f
No account linked to committer's email address
69 gewijzigde bestanden met toevoegingen van 2370 en 164 verwijderingen
  1. 5
    0
      web/app/components/base/icons/assets/vender/line/editor/bold-01.svg
  2. 5
    0
      web/app/components/base/icons/assets/vender/line/editor/dotpoints-01.svg
  3. 3
    0
      web/app/components/base/icons/assets/vender/line/editor/italic-01.svg
  4. 5
    0
      web/app/components/base/icons/assets/vender/line/editor/strikethrough-01.svg
  5. 8
    0
      web/app/components/base/icons/assets/vender/line/editor/title-case.svg
  6. 5
    0
      web/app/components/base/icons/assets/vender/line/files/sticker-square.svg
  7. BIN
      web/app/components/base/icons/assets/vender/line/general/Workflow.zip
  8. 5
    0
      web/app/components/base/icons/assets/vender/line/general/link-01.svg
  9. 10
    0
      web/app/components/base/icons/assets/vender/line/general/link-broken-01.svg
  10. 39
    0
      web/app/components/base/icons/src/vender/line/editor/Bold01.json
  11. 16
    0
      web/app/components/base/icons/src/vender/line/editor/Bold01.tsx
  12. 39
    0
      web/app/components/base/icons/src/vender/line/editor/Dotpoints01.json
  13. 16
    0
      web/app/components/base/icons/src/vender/line/editor/Dotpoints01.tsx
  14. 29
    0
      web/app/components/base/icons/src/vender/line/editor/Italic01.json
  15. 16
    0
      web/app/components/base/icons/src/vender/line/editor/Italic01.tsx
  16. 39
    0
      web/app/components/base/icons/src/vender/line/editor/Strikethrough01.json
  17. 16
    0
      web/app/components/base/icons/src/vender/line/editor/Strikethrough01.tsx
  18. 53
    0
      web/app/components/base/icons/src/vender/line/editor/TitleCase.json
  19. 16
    0
      web/app/components/base/icons/src/vender/line/editor/TitleCase.tsx
  20. 5
    0
      web/app/components/base/icons/src/vender/line/editor/index.ts
  21. 39
    0
      web/app/components/base/icons/src/vender/line/files/StickerSquare.json
  22. 16
    0
      web/app/components/base/icons/src/vender/line/files/StickerSquare.tsx
  23. 1
    0
      web/app/components/base/icons/src/vender/line/files/index.ts
  24. 39
    0
      web/app/components/base/icons/src/vender/line/general/Link01.json
  25. 16
    0
      web/app/components/base/icons/src/vender/line/general/Link01.tsx
  26. 66
    0
      web/app/components/base/icons/src/vender/line/general/LinkBroken01.json
  27. 16
    0
      web/app/components/base/icons/src/vender/line/general/LinkBroken01.tsx
  28. 2
    0
      web/app/components/base/icons/src/vender/line/general/index.ts
  29. 18
    1
      web/app/components/workflow/candidate-node.tsx
  30. 1
    0
      web/app/components/workflow/constants.ts
  31. 21
    15
      web/app/components/workflow/hooks/use-checklist.ts
  32. 2
    1
      web/app/components/workflow/hooks/use-node-data-update.ts
  33. 19
    2
      web/app/components/workflow/hooks/use-nodes-interactions.ts
  34. 7
    2
      web/app/components/workflow/hooks/use-workflow.ts
  35. 6
    2
      web/app/components/workflow/index.tsx
  36. 12
    3
      web/app/components/workflow/nodes/_base/components/node-resizer.tsx
  37. 2
    0
      web/app/components/workflow/nodes/constants.ts
  38. 21
    7
      web/app/components/workflow/nodes/index.tsx
  39. 42
    0
      web/app/components/workflow/note-node/constants.ts
  40. 29
    0
      web/app/components/workflow/note-node/hooks.ts
  41. 127
    0
      web/app/components/workflow/note-node/index.tsx
  42. 65
    0
      web/app/components/workflow/note-node/note-editor/context.tsx
  43. 62
    0
      web/app/components/workflow/note-node/note-editor/editor.tsx
  44. 3
    0
      web/app/components/workflow/note-node/note-editor/index.tsx
  45. 78
    0
      web/app/components/workflow/note-node/note-editor/plugins/format-detector-plugin/hooks.ts
  46. 9
    0
      web/app/components/workflow/note-node/note-editor/plugins/format-detector-plugin/index.tsx
  47. 152
    0
      web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx
  48. 115
    0
      web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts
  49. 25
    0
      web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/index.tsx
  50. 72
    0
      web/app/components/workflow/note-node/note-editor/store.ts
  51. 17
    0
      web/app/components/workflow/note-node/note-editor/theme/index.ts
  52. 24
    0
      web/app/components/workflow/note-node/note-editor/theme/theme.css
  53. 105
    0
      web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx
  54. 81
    0
      web/app/components/workflow/note-node/note-editor/toolbar/command.tsx
  55. 7
    0
      web/app/components/workflow/note-node/note-editor/toolbar/divider.tsx
  56. 86
    0
      web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx
  57. 147
    0
      web/app/components/workflow/note-node/note-editor/toolbar/hooks.ts
  58. 48
    0
      web/app/components/workflow/note-node/note-editor/toolbar/index.tsx
  59. 107
    0
      web/app/components/workflow/note-node/note-editor/toolbar/operator.tsx
  60. 21
    0
      web/app/components/workflow/note-node/note-editor/utils.ts
  61. 17
    0
      web/app/components/workflow/note-node/types.ts
  62. 27
    1
      web/app/components/workflow/operator/control.tsx
  63. 41
    0
      web/app/components/workflow/operator/hooks.ts
  64. 12
    0
      web/app/components/workflow/panel-contextmenu.tsx
  65. 6
    4
      web/app/components/workflow/utils.ts
  66. 19
    0
      web/i18n/en-US/workflow.ts
  67. 19
    0
      web/i18n/zh-Hans/workflow.ts
  68. 2
    2
      web/package.json
  69. 171
    124
      web/yarn.lock

+ 5
- 0
web/app/components/base/icons/assets/vender/line/editor/bold-01.svg Bestand weergeven

@@ -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>

+ 5
- 0
web/app/components/base/icons/assets/vender/line/editor/dotpoints-01.svg Bestand weergeven

@@ -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>

+ 3
- 0
web/app/components/base/icons/assets/vender/line/editor/italic-01.svg Bestand weergeven

@@ -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>

+ 5
- 0
web/app/components/base/icons/assets/vender/line/editor/strikethrough-01.svg Bestand weergeven

@@ -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>

+ 8
- 0
web/app/components/base/icons/assets/vender/line/editor/title-case.svg Bestand weergeven

@@ -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>

+ 5
- 0
web/app/components/base/icons/assets/vender/line/files/sticker-square.svg Bestand weergeven

@@ -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>

BIN
web/app/components/base/icons/assets/vender/line/general/Workflow.zip Bestand weergeven


+ 5
- 0
web/app/components/base/icons/assets/vender/line/general/link-01.svg Bestand weergeven

@@ -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>

+ 10
- 0
web/app/components/base/icons/assets/vender/line/general/link-broken-01.svg Bestand weergeven

@@ -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>

+ 39
- 0
web/app/components/base/icons/src/vender/line/editor/Bold01.json Bestand weergeven

@@ -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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/editor/Bold01.tsx Bestand weergeven

@@ -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

+ 39
- 0
web/app/components/base/icons/src/vender/line/editor/Dotpoints01.json Bestand weergeven

@@ -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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/editor/Dotpoints01.tsx Bestand weergeven

@@ -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

+ 29
- 0
web/app/components/base/icons/src/vender/line/editor/Italic01.json Bestand weergeven

@@ -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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/editor/Italic01.tsx Bestand weergeven

@@ -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

+ 39
- 0
web/app/components/base/icons/src/vender/line/editor/Strikethrough01.json Bestand weergeven

@@ -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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/editor/Strikethrough01.tsx Bestand weergeven

@@ -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

+ 53
- 0
web/app/components/base/icons/src/vender/line/editor/TitleCase.json Bestand weergeven

@@ -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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/editor/TitleCase.tsx Bestand weergeven

@@ -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

+ 5
- 0
web/app/components/base/icons/src/vender/line/editor/index.ts Bestand weergeven

@@ -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'

+ 39
- 0
web/app/components/base/icons/src/vender/line/files/StickerSquare.json Bestand weergeven

@@ -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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/files/StickerSquare.tsx Bestand weergeven

@@ -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

+ 1
- 0
web/app/components/base/icons/src/vender/line/files/index.ts Bestand weergeven

@@ -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'

+ 39
- 0
web/app/components/base/icons/src/vender/line/general/Link01.json Bestand weergeven

@@ -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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/general/Link01.tsx Bestand weergeven

@@ -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

+ 66
- 0
web/app/components/base/icons/src/vender/line/general/LinkBroken01.json Bestand weergeven

@@ -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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/general/LinkBroken01.tsx Bestand weergeven

@@ -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

+ 2
- 0
web/app/components/base/icons/src/vender/line/general/index.ts Bestand weergeven

@@ -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'

+ 18
- 1
web/app/components/workflow/candidate-node.tsx Bestand weergeven

@@ -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>
)
}

+ 1
- 0
web/app/components/workflow/constants.ts Bestand weergeven

@@ -391,3 +391,4 @@ export const PARAMETER_EXTRACTOR_COMMON_STRUCT: Var[] = [
]

export const WORKFLOW_DATA_UPDATE = 'WORKFLOW_DATA_UPDATE'
export const CUSTOM_NODE = 'custom'

+ 21
- 15
web/app/components/workflow/hooks/use-checklist.ts Bestand weergeven

@@ -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 }) })

+ 2
- 1
web/app/components/workflow/hooks/use-node-data-update.ts Bestand weergeven

@@ -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])

+ 19
- 2
web/app/components/workflow/hooks/use-nodes-interactions.ts Bestand weergeven

@@ -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,

+ 7
- 2
web/app/components/workflow/hooks/use-workflow.ts Bestand weergeven

@@ -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]

+ 6
- 2
web/app/components/workflow/index.tsx Bestand weergeven

@@ -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 = {

+ 12
- 3
web/app/components/workflow/nodes/_base/components/node-resizer.tsx Bestand weergeven

@@ -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>
)

+ 2
- 0
web/app/components/workflow/nodes/constants.ts Bestand weergeven

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

export const CUSTOM_NODE_TYPE = 'custom'

+ 21
- 7
web/app/components/workflow/nodes/index.tsx Bestand weergeven

@@ -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'

+ 42
- 0
web/app/components/workflow/note-node/constants.ts Bestand weergeven

@@ -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',
},
}

+ 29
- 0
web/app/components/workflow/note-node/hooks.ts Bestand weergeven

@@ -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,
}
}

+ 127
- 0
web/app/components/workflow/note-node/index.tsx Bestand weergeven

@@ -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)

+ 65
- 0
web/app/components/workflow/note-node/note-editor/context.tsx Bestand weergeven

@@ -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

+ 62
- 0
web/app/components/workflow/note-node/note-editor/editor.tsx Bestand weergeven

@@ -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)

+ 3
- 0
web/app/components/workflow/note-node/note-editor/index.tsx Bestand weergeven

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

+ 78
- 0
web/app/components/workflow/note-node/note-editor/plugins/format-detector-plugin/hooks.ts Bestand weergeven

@@ -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,
}
}

+ 9
- 0
web/app/components/workflow/note-node/note-editor/plugins/format-detector-plugin/index.tsx Bestand weergeven

@@ -0,0 +1,9 @@
import { useFormatDetector } from './hooks'

const FormatDetectorPlugin = () => {
useFormatDetector()

return null
}

export default FormatDetectorPlugin

+ 152
- 0
web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx Bestand weergeven

@@ -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)

+ 115
- 0
web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts Bestand weergeven

@@ -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,
}
}

+ 25
- 0
web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/index.tsx Bestand weergeven

@@ -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)

+ 72
- 0
web/app/components/workflow/note-node/note-editor/store.ts Bestand weergeven

@@ -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)!
}

+ 17
- 0
web/app/components/workflow/note-node/note-editor/theme/index.ts Bestand weergeven

@@ -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

+ 24
- 0
web/app/components/workflow/note-node/note-editor/theme/theme.css Bestand weergeven

@@ -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;
}

+ 105
- 0
web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx Bestand weergeven

@@ -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)

+ 81
- 0
web/app/components/workflow/note-node/note-editor/toolbar/command.tsx Bestand weergeven

@@ -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)

+ 7
- 0
web/app/components/workflow/note-node/note-editor/toolbar/divider.tsx Bestand weergeven

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

export default Divider

+ 86
- 0
web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx Bestand weergeven

@@ -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)

+ 147
- 0
web/app/components/workflow/note-node/note-editor/toolbar/hooks.ts Bestand weergeven

@@ -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,
}
}

+ 48
- 0
web/app/components/workflow/note-node/note-editor/toolbar/index.tsx Bestand weergeven

@@ -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)

+ 107
- 0
web/app/components/workflow/note-node/note-editor/toolbar/operator.tsx Bestand weergeven

@@ -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)

+ 21
- 0
web/app/components/workflow/note-node/note-editor/utils.ts Bestand weergeven

@@ -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]*))?)/

+ 17
- 0
web/app/components/workflow/note-node/types.ts Bestand weergeven

@@ -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
}

+ 27
- 1
web/app/components/workflow/operator/control.tsx Bestand weergeven

@@ -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

+ 41
- 0
web/app/components/workflow/operator/hooks.ts Bestand weergeven

@@ -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,
}
}

+ 12
- 0
web/app/components/workflow/panel-contextmenu.tsx Bestand weergeven

@@ -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={() => {

+ 6
- 4
web/app/components/workflow/utils.ts Bestand weergeven

@@ -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,

+ 19
- 0
web/i18n/en-US/workflow.ts Bestand weergeven

@@ -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}}',

+ 19
- 0
web/i18n/zh-Hans/workflow.ts Bestand weergeven

@@ -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}}终止',

+ 2
- 2
web/package.json Bestand weergeven

@@ -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",

+ 171
- 124
web/yarn.lock Bestand weergeven

@@ -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/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/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/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/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/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/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/utils" "0.12.2"
"@lexical/utils" "0.16.0"
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/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/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/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/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/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/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/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/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/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/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/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.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/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.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/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.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/react/-/react-0.12.2.tgz"
integrity sha512-ZBUvf5xmhiYWBw8pPrhYmLAEwFWrbF/cd15y76TUKD9l/2zDwwPs6nJQxBzfz3ei65r2/nnavLDV8W3QfvxfUA==
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/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/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/clipboard" "0.16.0"
"@lexical/selection" "0.16.0"
"@lexical/utils" "0.16.0"
lexical "0.16.0"

"@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/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 "0.16.0"

"@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/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/utils" "0.12.2"
"@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/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.12.2":
version "0.12.2"
resolved "https://registry.npmjs.org/@lexical/utils/-/utils-0.12.2.tgz"
integrity sha512-xW4y4l2Yd37+qLwkBvBGyzsKCA9wnh1ljphBJeR2vreT193i2gaIwuku2ZKlER14VHw4192qNJF7vUoAEmwurQ==
"@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.12.2"
"@lexical/selection" "0.12.2"
"@lexical/table" "0.12.2"
"@lexical/list" "0.16.0"
"@lexical/selection" "0.16.0"
"@lexical/table" "0.16.0"
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/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.12.2"
"@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"

Laden…
Annuleren
Opslaan