Merge remote-tracking branch 'myori/main' into feat/collaboration2

This commit is contained in:
hjlarry
2026-01-17 10:22:41 +08:00
6266 changed files with 544217 additions and 224655 deletions

View File

@ -2,7 +2,7 @@ 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 }> = {
export const THEME_MAP: Record<string, { outer: string, title: string, bg: string, border: string }> = {
[NoteTheme.blue]: {
outer: 'border-util-colors-blue-blue-500',
title: 'bg-util-colors-blue-blue-100',

View File

@ -1,7 +1,7 @@
import { useCallback } from 'react'
import type { EditorState } from 'lexical'
import { WorkflowHistoryEvent, useNodeDataUpdate, useWorkflowHistory } from '../hooks'
import type { NoteTheme } from './types'
import { useCallback } from 'react'
import { useNodeDataUpdate, useWorkflowHistory, WorkflowHistoryEvent } from '../hooks'
export const useNote = (id: string) => {
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()

View File

@ -1,26 +1,26 @@
import type { NodeProps } from 'reactflow'
import type { NoteNodeType } from './types'
import { useClickAway } from 'ahooks'
import {
memo,
useRef,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useClickAway } from 'ahooks'
import type { NodeProps } from 'reactflow'
import NodeResizer from '../nodes/_base/components/node-resizer'
import { useWorkflowHistoryStore } from '../workflow-history-store'
import { cn } from '@/utils/classnames'
import {
useNodeDataUpdate,
useNodesInteractions,
} from '../hooks'
import NodeResizer from '../nodes/_base/components/node-resizer'
import { useStore } from '../store'
import { useWorkflowHistoryStore } from '../workflow-history-store'
import { THEME_MAP } from './constants'
import { useNote } from './hooks'
import {
NoteEditor,
NoteEditorContextProvider,
NoteEditorToolbar,
} from './note-editor'
import { THEME_MAP } from './constants'
import { useNote } from './hooks'
import type { NoteNodeType } from './types'
import cn from '@/utils/classnames'
const Icon = () => {
return (
@ -90,10 +90,12 @@ const NoteNode = ({
className={cn(
'h-2 shrink-0 rounded-t-md opacity-50',
THEME_MAP[theme].title,
)}></div>
)}
>
</div>
{
data.selected && !data._isTempNode && (
<div className='absolute left-1/2 top-[-41px] -translate-x-1/2'>
<div className="absolute left-1/2 top-[-41px] -translate-x-1/2">
<NoteEditorToolbar
theme={theme}
onThemeChange={handleThemeChange}
@ -106,13 +108,14 @@ const NoteNode = ({
</div>
)
}
<div className='grow overflow-y-auto px-3 py-2.5'>
<div className="grow overflow-y-auto px-3 py-2.5">
<div className={cn(
data.selected && 'nodrag nopan nowheel cursor-text',
)}>
)}
>
<NoteEditor
containerElement={ref.current}
placeholder={t('workflow.nodes.note.editor.placeholder') || ''}
placeholder={t('nodes.note.editor.placeholder', { ns: 'workflow' }) || ''}
onChange={handleEditorChange}
setShortcutsEnabled={setShortcutsEnabled}
/>
@ -120,7 +123,7 @@ const NoteNode = ({
</div>
{
data.showAuthor && (
<div className='p-3 pt-0 text-xs text-text-tertiary'>
<div className="p-3 pt-0 text-xs text-text-tertiary">
{data.author}
</div>
)

View File

@ -1,19 +1,19 @@
'use client'
import { LinkNode } from '@lexical/link'
import {
ListItemNode,
ListNode,
} from '@lexical/list'
import { LexicalComposer } from '@lexical/react/LexicalComposer'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { $getRoot } from 'lexical'
import {
createContext,
memo,
useEffect,
useRef,
} from 'react'
import { LexicalComposer } from '@lexical/react/LexicalComposer'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { LinkNode } from '@lexical/link'
import {
ListItemNode,
ListNode,
} from '@lexical/list'
import { $getRoot } from 'lexical'
import { createNoteEditorStore } from './store'
import theme from './theme'

View File

@ -1,22 +1,22 @@
'use client'
import type { EditorState } from 'lexical'
import { ClickableLinkPlugin } from '@lexical/react/LexicalClickableLinkPlugin'
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin'
import { ListPlugin } from '@lexical/react/LexicalListPlugin'
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
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'
import FormatDetectorPlugin from './plugins/format-detector-plugin'
import LinkEditorPlugin from './plugins/link-editor-plugin'
type EditorProps = {
placeholder?: string
@ -35,18 +35,18 @@ const Editor = ({
}, [onChange])
return (
<div className='relative'>
<div className="relative">
<RichTextPlugin
contentEditable={
contentEditable={(
<div>
<ContentEditable
onFocus={() => setShortcutsEnabled?.(false)}
onBlur={() => setShortcutsEnabled?.(true)}
spellCheck={false}
className='h-full w-full text-text-secondary caret-primary-600 outline-none'
className="h-full w-full text-text-secondary caret-primary-600 outline-none"
/>
</div>
}
)}
placeholder={<Placeholder value={placeholder} compact />}
ErrorBoundary={LexicalErrorBoundary}
/>

View File

@ -1,18 +1,18 @@
import {
useCallback,
useEffect,
} from 'react'
import type { LinkNode } from '@lexical/link'
import { $isLinkNode } from '@lexical/link'
import { $isListItemNode } from '@lexical/list'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { mergeRegister } from '@lexical/utils'
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 {
useCallback,
useEffect,
} from 'react'
import { useNoteEditorStore } from '../../store'
import { getSelectedNode } from '../../utils'
export const useFormatDetector = () => {
const [editor] = useLexicalComposerContext()

View File

@ -1,27 +1,27 @@
import {
memo,
useEffect,
useState,
} from 'react'
import { escape } from 'lodash-es'
import {
FloatingPortal,
flip,
FloatingPortal,
offset,
shift,
useFloating,
} from '@floating-ui/react'
import { useTranslation } from 'react-i18next'
import { useClickAway } from 'ahooks'
import {
RiEditLine,
RiExternalLinkLine,
RiLinkUnlinkM,
} from '@remixicon/react'
import { useClickAway } from 'ahooks'
import { escape } from 'es-toolkit/string'
import {
memo,
useEffect,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import { cn } from '@/utils/classnames'
import { useStore } from '../../store'
import { useLink } from './hooks'
import cn from '@/utils/classnames'
import Button from '@/app/components/base/button'
type LinkEditorComponentProps = {
containerElement: HTMLDivElement | null
@ -80,19 +80,19 @@ const LinkEditorComponent = ({
!linkOperatorShow && (
<>
<input
className='mr-0.5 h-6 w-[196px] appearance-none rounded-sm bg-transparent p-1 text-[13px] text-components-input-text-filled outline-none'
className="mr-0.5 h-6 w-[196px] appearance-none rounded-sm bg-transparent p-1 text-[13px] text-components-input-text-filled outline-none"
value={url}
onChange={e => setUrl(e.target.value)}
placeholder={t('workflow.nodes.note.editor.enterUrl') || ''}
placeholder={t('nodes.note.editor.enterUrl', { ns: 'workflow' }) || ''}
autoFocus
/>
<Button
variant='primary'
size='small'
variant="primary"
size="small"
disabled={!url}
onClick={() => handleSaveLink(url)}
>
{t('common.operation.ok')}
{t('operation.ok', { ns: 'common' })}
</Button>
</>
)
@ -101,39 +101,39 @@ const LinkEditorComponent = ({
linkOperatorShow && (
<>
<a
className='flex h-6 items-center rounded-md px-2 hover:bg-state-base-hover'
className="flex h-6 items-center rounded-md px-2 hover:bg-state-base-hover"
href={escape(url)}
target='_blank'
rel='noreferrer'
target="_blank"
rel="noreferrer"
>
<RiExternalLinkLine className='mr-1 h-3 w-3' />
<div className='mr-1'>
{t('workflow.nodes.note.editor.openLink')}
<RiExternalLinkLine className="mr-1 h-3 w-3" />
<div className="mr-1">
{t('nodes.note.editor.openLink', { ns: 'workflow' })}
</div>
<div
title={escape(url)}
className='max-w-[140px] truncate text-text-accent'
className="max-w-[140px] truncate text-text-accent"
>
{escape(url)}
</div>
</a>
<div className='mx-1 h-3.5 w-[1px] bg-divider-regular'></div>
<div className="mx-1 h-3.5 w-[1px] bg-divider-regular"></div>
<div
className='mr-0.5 flex h-6 cursor-pointer items-center rounded-md px-2 hover:bg-state-base-hover'
className="mr-0.5 flex h-6 cursor-pointer items-center rounded-md px-2 hover:bg-state-base-hover"
onClick={(e) => {
e.stopPropagation()
setLinkOperatorShow(false)
}}
>
<RiEditLine className='mr-1 h-3 w-3' />
{t('common.operation.edit')}
<RiEditLine className="mr-1 h-3 w-3" />
{t('operation.edit', { ns: 'common' })}
</div>
<div
className='flex h-6 cursor-pointer items-center rounded-md px-2 hover:bg-state-base-hover'
className="flex h-6 cursor-pointer items-center rounded-md px-2 hover:bg-state-base-hover"
onClick={handleUnlink}
>
<RiLinkUnlinkM className='mr-1 h-3 w-3' />
{t('workflow.nodes.note.editor.unlink')}
<RiLinkUnlinkM className="mr-1 h-3 w-3" />
{t('nodes.note.editor.unlink', { ns: 'workflow' })}
</div>
</>
)

View File

@ -1,23 +1,23 @@
import {
useCallback,
useEffect,
} from 'react'
import { useTranslation } from 'react-i18next'
TOGGLE_LINK_COMMAND,
} from '@lexical/link'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import {
mergeRegister,
} from '@lexical/utils'
import { escape } from 'es-toolkit/string'
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'
useCallback,
useEffect,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useToastContext } from '@/app/components/base/toast'
import { useNoteEditorStore } from '../../store'
import { urlRegExp } from '../../utils'
import { useToastContext } from '@/app/components/base/toast'
export const useOpenLink = () => {
const [editor] = useLexicalComposerContext()
@ -92,7 +92,7 @@ export const useLink = () => {
const handleSaveLink = useCallback((url: string) => {
if (url && !urlRegExp.test(url)) {
notify({ type: 'error', message: t('workflow.nodes.note.editor.invalidUrl') })
notify({ type: 'error', message: t('nodes.note.editor.invalidUrl', { ns: 'workflow' }) })
return
}
editor.dispatchCommand(TOGGLE_LINK_COMMAND, escape(url))

View File

@ -2,8 +2,8 @@ import {
memo,
} from 'react'
import { useStore } from '../../store'
import { useOpenLink } from './hooks'
import LinkEditorComponent from './component'
import { useOpenLink } from './hooks'
type LinkEditorPluginProps = {
containerElement: HTMLDivElement | null

View File

@ -2,14 +2,14 @@ import {
memo,
useState,
} from 'react'
import { NoteTheme } from '../../types'
import { THEME_MAP } from '../../constants'
import cn from '@/utils/classnames'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { cn } from '@/utils/classnames'
import { THEME_MAP } from '../../constants'
import { NoteTheme } from '../../types'
export const COLOR_LIST = [
{
@ -58,29 +58,31 @@ const ColorPicker = ({
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='top'
placement="top"
offset={4}
>
<PortalToFollowElemTrigger onClick={() => setOpen(!open)}>
<div className={cn(
'flex h-8 w-8 cursor-pointer items-center justify-center rounded-md hover:bg-black/5',
open && 'bg-black/5',
)}>
)}
>
<div
className={cn(
'h-4 w-4 rounded-full border border-black/5',
THEME_MAP[theme].title,
)}
></div>
>
</div>
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='grid grid-cols-3 grid-rows-2 gap-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-lg'>
<div className="grid grid-cols-3 grid-rows-2 gap-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-lg">
{
COLOR_LIST.map(color => (
<div
key={color.key}
className='group relative flex h-8 w-8 cursor-pointer items-center justify-center rounded-md'
className="group relative flex h-8 w-8 cursor-pointer items-center justify-center rounded-md"
onClick={(e) => {
e.stopPropagation()
onThemeChange(color.key)
@ -92,13 +94,15 @@ const ColorPicker = ({
'absolute left-1/2 top-1/2 hidden h-5 w-5 -translate-x-1/2 -translate-y-1/2 rounded-full border-[1.5px] group-hover:block',
color.outer,
)}
></div>
>
</div>
<div
className={cn(
'absolute left-1/2 top-1/2 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border border-black/5',
color.inner,
)}
></div>
>
</div>
</div>
))
}

View File

@ -1,8 +1,3 @@
import {
memo,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
RiBold,
RiItalic,
@ -10,10 +5,15 @@ import {
RiListUnordered,
RiStrikethrough,
} from '@remixicon/react'
import {
memo,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import { cn } from '@/utils/classnames'
import { useStore } from '../store'
import { useCommand } from './hooks'
import cn from '@/utils/classnames'
import Tooltip from '@/app/components/base/tooltip'
type CommandProps = {
type: 'bold' | 'italic' | 'strikethrough' | 'link' | 'bullet'
@ -47,15 +47,15 @@ const Command = ({
const tip = useMemo(() => {
switch (type) {
case 'bold':
return t('workflow.nodes.note.editor.bold')
return t('nodes.note.editor.bold', { ns: 'workflow' })
case 'italic':
return t('workflow.nodes.note.editor.italic')
return t('nodes.note.editor.italic', { ns: 'workflow' })
case 'strikethrough':
return t('workflow.nodes.note.editor.strikethrough')
return t('nodes.note.editor.strikethrough', { ns: 'workflow' })
case 'link':
return t('workflow.nodes.note.editor.link')
return t('nodes.note.editor.link', { ns: 'workflow' })
case 'bullet':
return t('workflow.nodes.note.editor.bulletList')
return t('nodes.note.editor.bulletList', { ns: 'workflow' })
}
}, [type, t])

View File

@ -1,6 +1,6 @@
const Divider = () => {
return (
<div className='mx-1 h-3.5 w-[1px] bg-divider-regular'></div>
<div className="mx-1 h-3.5 w-[1px] bg-divider-regular"></div>
)
}

View File

@ -1,29 +1,29 @@
import { memo } from 'react'
import { RiFontSize } from '@remixicon/react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { useFontSize } from './hooks'
import cn from '@/utils/classnames'
import { Check } from '@/app/components/base/icons/src/vender/line/general'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { Check } from '@/app/components/base/icons/src/vender/line/general'
import { cn } from '@/utils/classnames'
import { useFontSize } from './hooks'
const FontSizeSelector = () => {
const { t } = useTranslation()
const FONT_SIZE_LIST = [
{
key: '12px',
value: t('workflow.nodes.note.editor.small'),
value: t('nodes.note.editor.small', { ns: 'workflow' }),
},
{
key: '14px',
value: t('workflow.nodes.note.editor.medium'),
value: t('nodes.note.editor.medium', { ns: 'workflow' }),
},
{
key: '16px',
value: t('workflow.nodes.note.editor.large'),
value: t('nodes.note.editor.large', { ns: 'workflow' }),
},
]
const {
@ -37,25 +37,26 @@ const FontSizeSelector = () => {
<PortalToFollowElem
open={fontSizeSelectorShow}
onOpenChange={handleOpenFontSizeSelector}
placement='bottom-start'
placement="bottom-start"
offset={2}
>
<PortalToFollowElemTrigger onClick={() => handleOpenFontSizeSelector(!fontSizeSelectorShow)}>
<div className={cn(
'flex h-8 cursor-pointer items-center rounded-md pl-2 pr-1.5 text-[13px] font-medium text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
fontSizeSelectorShow && 'bg-state-base-hover text-text-secondary',
)}>
<RiFontSize className='mr-1 h-4 w-4' />
{FONT_SIZE_LIST.find(font => font.key === fontSize)?.value || t('workflow.nodes.note.editor.small')}
)}
>
<RiFontSize className="mr-1 h-4 w-4" />
{FONT_SIZE_LIST.find(font => font.key === fontSize)?.value || t('nodes.note.editor.small', { ns: 'workflow' })}
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='w-[120px] rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 text-text-secondary shadow-xl'>
<div className="w-[120px] rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 text-text-secondary shadow-xl">
{
FONT_SIZE_LIST.map(font => (
<div
key={font.key}
className='flex h-8 cursor-pointer items-center justify-between rounded-md pl-3 pr-2 hover:bg-state-base-hover'
className="flex h-8 cursor-pointer items-center justify-between rounded-md pl-3 pr-2 hover:bg-state-base-hover"
onClick={(e) => {
e.stopPropagation()
handleFontSize(font.key)
@ -69,7 +70,7 @@ const FontSizeSelector = () => {
</div>
{
fontSize === font.key && (
<Check className='h-4 w-4 text-text-accent' />
<Check className="h-4 w-4 text-text-accent" />
)
}
</div>

View File

@ -1,8 +1,15 @@
import {
useCallback,
useEffect,
useState,
} from 'react'
$isLinkNode,
TOGGLE_LINK_COMMAND,
} from '@lexical/link'
import { INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import {
$getSelectionStyleValueForProperty,
$patchStyleText,
$setBlocksType,
} from '@lexical/selection'
import { mergeRegister } from '@lexical/utils'
import {
$createParagraphNode,
$getSelection,
@ -13,17 +20,10 @@ import {
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'
useCallback,
useEffect,
useState,
} from 'react'
import { useNoteEditorStore } from '../store'
import { getSelectedNode } from '../utils'

View File

@ -1,10 +1,10 @@
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 { memo } from 'react'
import ColorPicker from './color-picker'
import Command from './command'
import Divider from './divider'
import FontSizeSelector from './font-size-selector'
import Operator from './operator'
type ToolbarProps = ColorPickerProps & OperatorProps
@ -18,7 +18,7 @@ const Toolbar = ({
onShowAuthorChange,
}: ToolbarProps) => {
return (
<div className='inline-flex items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-sm'>
<div className="inline-flex items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-sm">
<ColorPicker
theme={theme}
onThemeChange={onThemeChange}
@ -26,12 +26,12 @@ const Toolbar = ({
<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 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

View File

@ -1,17 +1,17 @@
import { RiMoreFill } from '@remixicon/react'
import {
memo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { RiMoreFill } from '@remixicon/react'
import cn from '@/utils/classnames'
import ShortcutsName from '@/app/components/workflow/shortcuts-name'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Switch from '@/app/components/base/switch'
import ShortcutsName from '@/app/components/workflow/shortcuts-name'
import { cn } from '@/utils/classnames'
export type OperatorProps = {
onCopy: () => void
@ -34,7 +34,7 @@ const Operator = ({
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
placement="bottom-end"
offset={4}
>
<PortalToFollowElemTrigger onClick={() => setOpen(!open)}>
@ -44,57 +44,57 @@ const Operator = ({
open && 'bg-state-base-hover text-text-secondary',
)}
>
<RiMoreFill className='h-4 w-4' />
<RiMoreFill className="h-4 w-4" />
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='min-w-[192px] rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl'>
<div className='p-1'>
<div className="min-w-[192px] rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl">
<div className="p-1">
<div
className='flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover'
className="flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover"
onClick={() => {
onCopy()
setOpen(false)
}}
>
{t('workflow.common.copy')}
{t('common.copy', { ns: 'workflow' })}
<ShortcutsName keys={['ctrl', 'c']} />
</div>
<div
className='flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover'
className="flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover"
onClick={() => {
onDuplicate()
setOpen(false)
}}
>
{t('workflow.common.duplicate')}
{t('common.duplicate', { ns: 'workflow' })}
<ShortcutsName keys={['ctrl', 'd']} />
</div>
</div>
<div className='h-px bg-divider-subtle'></div>
<div className='p-1'>
<div className="h-px bg-divider-subtle"></div>
<div className="p-1">
<div
className='flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover'
className="flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover"
onClick={e => e.stopPropagation()}
>
<div>{t('workflow.nodes.note.editor.showAuthor')}</div>
<div>{t('nodes.note.editor.showAuthor', { ns: 'workflow' })}</div>
<Switch
size='l'
size="l"
defaultValue={showAuthor}
onChange={onShowAuthorChange}
/>
</div>
</div>
<div className='h-px bg-divider-subtle'></div>
<div className='p-1'>
<div className="h-px bg-divider-subtle"></div>
<div className="p-1">
<div
className='flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive'
className="flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive"
onClick={() => {
onDelete()
setOpen(false)
}}
>
{t('common.operation.delete')}
{t('operation.delete', { ns: 'common' })}
<ShortcutsName keys={['del']} />
</div>
</div>

View File

@ -1,5 +1,5 @@
import { $isAtNodeEnd } from '@lexical/selection'
import type { ElementNode, RangeSelection, TextNode } from 'lexical'
import { $isAtNodeEnd } from '@lexical/selection'
export function getSelectedNode(
selection: RangeSelection,
@ -19,4 +19,4 @@ export function getSelectedNode(
}
// eslint-disable-next-line sonarjs/empty-string-repetition
export const urlRegExp = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/
export const urlRegExp = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-]*)?\??[-+=&;%@.\w]*#?\w*)?)/