mirror of
https://github.com/langgenius/dify.git
synced 2026-04-24 12:55:49 +08:00
Merge remote-tracking branch 'myori/main' into feat/collaboration2
This commit is contained in:
@ -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',
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
))
|
||||
}
|
||||
|
||||
@ -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])
|
||||
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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*)?)/
|
||||
|
||||
Reference in New Issue
Block a user