Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
4814d6ea
Commit
4814d6ea
authored
Jun 08, 2023
by
StyleZhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add notion-page-selector
parent
62a5a1ea
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
272 additions
and
51 deletions
+272
-51
index.module.css
web/app/components/base/checkbox/index.module.css
+0
-0
index.tsx
web/app/components/base/checkbox/index.tsx
+18
-0
index.module.css
web/app/components/base/notion-icon/index.module.css
+4
-2
index.tsx
web/app/components/base/notion-icon/index.tsx
+21
-4
base.tsx
web/app/components/base/notion-page-selector/base.tsx
+25
-9
index.module.css
...page-selector/notion-page-selector-modal/index.module.css
+28
-0
index.tsx
...notion-page-selector/notion-page-selector-modal/index.tsx
+38
-2
index.module.css
.../base/notion-page-selector/page-selector/index.module.css
+10
-0
index.tsx
...ponents/base/notion-page-selector/page-selector/index.tsx
+70
-0
index.module.css
.../notion-page-selector/workspace-selector/index.module.css
+1
-0
index.tsx
...ts/base/notion-page-selector/workspace-selector/index.tsx
+41
-23
index.tsx
web/app/components/header/index.tsx
+1
-0
common.ts
web/models/common.ts
+15
-11
No files found.
web/app/components/base/checkbox/index.module.css
0 → 100644
View file @
4814d6ea
web/app/components/base/checkbox/index.tsx
0 → 100644
View file @
4814d6ea
import
cn
from
'classnames'
type
CheckboxProps
=
{
checked
?:
boolean
onCheck
?:
()
=>
void
className
?:
string
}
const
Checkbox
=
({
checked
,
onCheck
,
className
}:
CheckboxProps
)
=>
{
return
(
<
div
className=
{
cn
(
'w-4 h-4 border rounded'
,
checked
?
'border-primary-600 bg-primary-600'
:
'border-gray-300'
,
className
)
}
onClick=
{
onCheck
}
/>
)
}
export
default
Checkbox
web/app/components/base/notion-icon/index.module.css
View file @
4814d6ea
.icon
{
.default-page-icon
{
background
:
center
center
no-repeat
;
width
:
20px
;
height
:
20px
;
background
:
url(../notion-page-selector/assets/notion-page.svg)
center
center
no-repeat
;
background-size
:
cover
;
background-size
:
cover
;
}
}
\ No newline at end of file
web/app/components/base/notion-icon/index.tsx
View file @
4814d6ea
import
cn
from
'classnames'
import
cn
from
'classnames'
import
s
from
'./index.module.css'
type
NotionIconProps
=
{
type
NotionIconProps
=
{
type
?:
'workspace'
|
'page'
type
?:
'workspace'
|
'page'
src
:
string
src
?:
string
|
null
name
:
string
name
?:
string
|
null
className
?:
string
className
?:
string
}
}
...
@@ -29,12 +30,28 @@ const NotionIcon = ({
...
@@ -29,12 +30,28 @@ const NotionIcon = ({
)
)
}
}
return
(
return
(
<
div
className=
{
cn
(
'flex items-center justify-center w-5 h-5 bg-gray-200 text-xs font-medium text-gray-500 rounded'
,
className
)
}
>
{
name
[
0
].
toLocaleUpperCase
()
}
</
div
>
<
div
className=
{
cn
(
'flex items-center justify-center w-5 h-5 bg-gray-200 text-xs font-medium text-gray-500 rounded'
,
className
)
}
>
{
name
?.[
0
].
toLocaleUpperCase
()
}
</
div
>
)
}
if
(
src
)
{
return
(
<
img
alt=
'workspace icon'
src=
{
src
}
className=
{
cn
(
'block object-cover w-5 h-5'
,
className
)
}
/>
)
}
if
(
name
)
{
return
(
<
div
className=
{
cn
(
'flex items-center justify-center w-5 h-5'
,
className
)
}
>
{
src
}
</
div
>
)
)
}
}
return
(
return
(
<
div
></
div
>
<
div
className=
{
cn
(
s
[
'default-page-icon'
],
className
)
}
/
>
)
)
}
}
...
...
web/app/components/base/notion-page-selector/base.tsx
View file @
4814d6ea
import
{
useState
}
from
'react'
import
{
useCallback
,
useEffect
,
useState
}
from
'react'
import
useSWR
from
'swr'
import
cn
from
'classnames'
import
cn
from
'classnames'
import
s
from
'./base.module.css'
import
s
from
'./base.module.css'
import
WorkspaceSelector
from
'./workspace-selector'
import
WorkspaceSelector
from
'./workspace-selector'
import
SearchInput
from
'./search-input'
import
SearchInput
from
'./search-input'
import
PageSelector
from
'./page-selector'
import
{
fetchDataSource
}
from
'@/service/common'
const
NotionPageSelector
=
()
=>
{
const
NotionPageSelector
=
()
=>
{
const
[
searchValue
,
setSearchValue
]
=
useState
(
''
)
const
[
searchValue
,
setSearchValue
]
=
useState
(
''
)
const
{
data
}
=
useSWR
({
url
:
'data-source/integrates'
},
fetchDataSource
)
const
notionWorkspaces
=
data
?.
data
.
filter
(
item
=>
item
.
provider
===
'notion'
)
||
[]
const
firstWorkspace
=
notionWorkspaces
[
0
]?.
id
const
[
currentWorkspaceId
,
setCurrentWorkspaceId
]
=
useState
(
''
)
const
currentWorkspace
=
notionWorkspaces
.
find
(
workspace
=>
workspace
.
id
===
currentWorkspaceId
)
const
handleSearchValueChange
=
(
value
:
string
)
=>
{
const
handleSearchValueChange
=
(
value
:
string
)
=>
{
setSearchValue
(
value
)
setSearchValue
(
value
)
}
}
const
handleSelectWorkspace
=
useCallback
((
workspaceId
:
string
)
=>
{
setCurrentWorkspaceId
(
workspaceId
)
},
[])
useEffect
(()
=>
{
setCurrentWorkspaceId
(
firstWorkspace
)
},
[
firstWorkspace
])
return
(
return
(
<
div
className=
'bg-gray-25 border border-gray-200 rounded-xl'
>
<
div
className=
'bg-gray-25 border border-gray-200 rounded-xl'
>
<
div
className=
'flex items-center pl-[10px] pr-2 h-11 bg-white'
>
<
div
className=
'flex items-center pl-[10px] pr-2 h-11 bg-white border-b border-b-gray-200 rounded-t-xl'
>
<
WorkspaceSelector
/>
<
WorkspaceSelector
value=
{
currentWorkspaceId
}
items=
{
notionWorkspaces
}
onSelect=
{
handleSelectWorkspace
}
/>
<
div
className=
'mx-1 w-[1px] h-3 bg-gray-200'
/>
<
div
className=
'mx-1 w-[1px] h-3 bg-gray-200'
/>
<
div
className=
{
cn
(
s
[
'setting-icon'
],
'w-6 h-6 cursor-pointer'
)
}
/>
<
div
className=
{
cn
(
s
[
'setting-icon'
],
'w-6 h-6 cursor-pointer'
)
}
/>
<
div
className=
'grow'
/>
<
div
className=
'grow'
/>
...
@@ -21,12 +41,8 @@ const NotionPageSelector = () => {
...
@@ -21,12 +41,8 @@ const NotionPageSelector = () => {
onChange=
{
handleSearchValueChange
}
onChange=
{
handleSearchValueChange
}
/>
/>
</
div
>
</
div
>
<
div
className=
'p-2'
>
<
div
className=
'rounded-b-xl overflow-hidden'
>
<
div
className=
'flex items-center px-2 h-7 rounded-md'
>
<
PageSelector
list=
{
currentWorkspace
?.
source_info
.
pages
||
[]
}
/>
<
div
className=
'mr-3 w-4 h-4'
></
div
>
<
div
className=
'mr-2 w-5 h-5'
></
div
>
<
div
className=
'text-sm font-medium text-gray-700'
>
sdfsfsfsd
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
)
)
...
...
web/app/components/base/notion-page-selector/notion-page-selector-modal/index.module.css
View file @
4814d6ea
.modal
{
width
:
600px
!important
;
max-width
:
600px
!important
;
padding
:
24px
32px
!important
;
}
.operate
{
padding
:
0
8px
;
min-width
:
96px
;
height
:
36px
;
line-height
:
36px
;
text-align
:
center
;
background-color
:
#ffffff
;
box-shadow
:
0px
1px
2px
rgba
(
16
,
24
,
40
,
0.05
);
border-radius
:
8px
;
border
:
0.5px
solid
#eaecf0
;
font-size
:
14px
;
font-weight
:
500
;
color
:
#667085
;
cursor
:
pointer
;
}
.operate-save
{
margin-left
:
8px
;
border-color
:
#155eef
;
background-color
:
#155eef
;
color
:
#ffffff
;
}
\ No newline at end of file
web/app/components/base/notion-page-selector/notion-page-selector-modal/index.tsx
View file @
4814d6ea
import
{
useTranslation
}
from
'react-i18next'
import
cn
from
'classnames'
import
{
XMarkIcon
}
from
'@heroicons/react/24/outline'
import
NotionPageSelector
from
'../base'
import
NotionPageSelector
from
'../base'
import
s
from
'./index.module.css'
import
Modal
from
'@/app/components/base/modal'
type
NotionPageSelectorModalProps
=
{
isShow
:
boolean
onClose
:
()
=>
void
}
const
NotionPageSelectorModal
=
({
isShow
,
onClose
,
}:
NotionPageSelectorModalProps
)
=>
{
const
{
t
}
=
useTranslation
()
const
handleClose
=
()
=>
{
onClose
()
}
const
NotionPageSelectorModal
=
()
=>
{
return
(
return
(
<
NotionPageSelector
/>
<
Modal
className=
{
s
.
modal
}
isShow=
{
isShow
}
onClose=
{
()
=>
{}
}
>
<
div
className=
'flex items-center justify-between mb-6 h-8'
>
<
div
className=
'text-xl font-semibold text-gray-900'
>
{
t
(
'common.dataSource.notion.selector.addPages'
)
}
</
div
>
<
div
className=
'flex items-center justify-center -mr-2 w-8 h-8 cursor-pointer'
onClick=
{
handleClose
}
>
<
XMarkIcon
className=
'w-4 h-4'
/>
</
div
>
</
div
>
<
NotionPageSelector
/>
<
div
className=
'mt-8 flex justify-end'
>
<
div
className=
{
s
.
operate
}
onClick=
{
handleClose
}
>
{
t
(
'common.operation.cancel'
)
}
</
div
>
<
div
className=
{
cn
(
s
.
operate
,
s
[
'operate-save'
])
}
>
{
t
(
'common.operation.save'
)
}
</
div
>
</
div
>
</
Modal
>
)
)
}
}
...
...
web/app/components/base/notion-page-selector/page-selector/index.module.css
0 → 100644
View file @
4814d6ea
.arrow
{
width
:
20px
;
height
:
20px
;
background
:
url(../assets/down-arrow.svg)
center
center
no-repeat
;
background-size
:
16px
16px
;
}
.arrow-collapse
{
transform
:
rotate
(
-90deg
);
}
\ No newline at end of file
web/app/components/base/notion-page-selector/page-selector/index.tsx
0 → 100644
View file @
4814d6ea
import
{
memo
}
from
'react'
import
{
FixedSizeList
as
List
,
areEqual
}
from
'react-window'
import
type
{
ListChildComponentProps
}
from
'react-window'
import
cn
from
'classnames'
import
Checkbox
from
'../../checkbox'
import
NotionIcon
from
'../../notion-icon'
import
s
from
'./index.module.css'
import
type
{
DataSourceNotionPage
}
from
'@/models/common'
const
Item
=
memo
(({
index
,
style
,
data
}:
ListChildComponentProps
<
{
list
:
DataSourceNotionPage
[]
}
>
)
=>
{
const
current
=
data
.
list
[
index
]
let
src
,
name
if
(
current
.
page_icon
)
{
try
{
const
icon
=
JSON
.
parse
(
current
.
page_icon
)
if
(
icon
?.
type
===
'emoji'
)
name
=
icon
?.
emoji
if
(
icon
?.
type
===
'external'
)
src
=
icon
?.
external
?.
url
}
catch
(
e
:
any
)
{}
}
return
(
<
div
className=
'group flex items-center px-2 rounded-md hover:bg-gray-100 cursor-pointer'
style=
{
{
...
style
,
top
:
style
.
top
as
number
+
8
,
left
:
8
,
right
:
8
,
width
:
'calc(100% - 16px)'
}
}
>
<
Checkbox
className=
'shrink-0 mr-2 group-hover:border-primary-600 group-hover:border-[2px]'
/>
<
div
className=
{
cn
(
s
.
arrow
,
s
[
'arrow-collapse'
],
'shrink-0 mr-1 w-5 h-5 hover:bg-gray-200 rounded-md'
)
}
/>
<
NotionIcon
className=
'shrink-0 mr-1'
type=
'page'
src=
{
src
}
name=
{
name
}
/>
<
div
className=
'text-sm font-medium text-gray-700 truncate'
title=
{
current
.
page_name
}
>
{
current
.
page_name
}
</
div
>
</
div
>
)
},
areEqual
)
type
PageSelectorProps
=
{
list
:
DataSourceNotionPage
[]
}
const
PageSelector
=
({
list
,
}:
PageSelectorProps
)
=>
{
return
(
<
List
className=
'py-2'
height=
{
296
}
itemCount=
{
list
.
length
}
itemSize=
{
28
}
width=
'100%'
itemData=
{
{
list
}
}
>
{
Item
}
</
List
>
)
}
export
default
PageSelector
web/app/components/base/notion-page-selector/workspace-selector/index.module.css
View file @
4814d6ea
...
@@ -5,4 +5,5 @@
...
@@ -5,4 +5,5 @@
.popup
{
.popup
{
box-shadow
:
0px
12px
16px
-4px
rgba
(
16
,
24
,
40
,
0.08
),
0px
4px
6px
-2px
rgba
(
16
,
24
,
40
,
0.03
);
box-shadow
:
0px
12px
16px
-4px
rgba
(
16
,
24
,
40
,
0.08
),
0px
4px
6px
-2px
rgba
(
16
,
24
,
40
,
0.03
);
z-index
:
10
;
}
}
\ No newline at end of file
web/app/components/base/notion-page-selector/workspace-selector/index.tsx
View file @
4814d6ea
...
@@ -3,10 +3,22 @@ import { useTranslation } from 'react-i18next'
...
@@ -3,10 +3,22 @@ import { useTranslation } from 'react-i18next'
import
{
Fragment
}
from
'react'
import
{
Fragment
}
from
'react'
import
{
Menu
,
Transition
}
from
'@headlessui/react'
import
{
Menu
,
Transition
}
from
'@headlessui/react'
import
cn
from
'classnames'
import
cn
from
'classnames'
import
NotionIcon
from
'../../notion-icon'
import
s
from
'./index.module.css'
import
s
from
'./index.module.css'
import
type
{
DataSourceNotion
}
from
'@/models/common'
export
default
function
WorkspaceSelector
()
{
type
WorkspaceSelectorProps
=
{
value
:
string
items
:
DataSourceNotion
[]
onSelect
:
(
v
:
string
)
=>
void
}
export
default
function
WorkspaceSelector
({
value
,
items
,
onSelect
,
}:
WorkspaceSelectorProps
)
{
const
{
t
}
=
useTranslation
()
const
{
t
}
=
useTranslation
()
const
currentWorkspace
=
items
.
find
(
item
=>
item
.
id
===
value
)?.
source_info
return
(
return
(
<
Menu
as=
"div"
className=
"relative inline-block text-left"
>
<
Menu
as=
"div"
className=
"relative inline-block text-left"
>
...
@@ -14,9 +26,13 @@ export default function WorkspaceSelector() {
...
@@ -14,9 +26,13 @@ export default function WorkspaceSelector() {
({
open
})
=>
(
({
open
})
=>
(
<>
<>
<
Menu
.
Button
className=
{
`flex items-center justify-center h-7 rounded-md hover:bg-gray-50 ${open && 'bg-gray-50'} cursor-pointer`
}
>
<
Menu
.
Button
className=
{
`flex items-center justify-center h-7 rounded-md hover:bg-gray-50 ${open && 'bg-gray-50'} cursor-pointer`
}
>
<
div
className=
'ml-1 mr-2 w-5 h-5 rounded'
></
div
>
<
NotionIcon
<
div
className=
'mr-1 w-[90px] truncate'
>
Stylezhang's workspace
</
div
>
className=
'ml-1 mr-2'
<
div
className=
'mr-1 w-5 h-[18px] bg-primary-50 rounded-lg text-xs font-medium text-primary-600'
>
4
</
div
>
src=
{
currentWorkspace
?.
workspace_icon
}
name=
{
currentWorkspace
?.
workspace_name
}
/>
<
div
className=
'mr-1 w-[90px] text-left text-sm font-medium text-gray-700 truncate'
title=
{
currentWorkspace
?.
workspace_name
}
>
{
currentWorkspace
?.
workspace_name
}
</
div
>
<
div
className=
'mr-1 w-5 h-[18px] bg-primary-50 rounded-lg text-xs font-medium text-primary-600'
>
{
currentWorkspace
?.
total
}
</
div
>
<
div
className=
{
cn
(
s
[
'down-arrow'
],
'mr-2 w-3 h-3'
)
}
/>
<
div
className=
{
cn
(
s
[
'down-arrow'
],
'mr-2 w-3 h-3'
)
}
/>
</
Menu
.
Button
>
</
Menu
.
Button
>
<
Transition
<
Transition
...
@@ -36,25 +52,27 @@ export default function WorkspaceSelector() {
...
@@ -36,25 +52,27 @@ export default function WorkspaceSelector() {
border-[0.5px] border-gray-200`
,
border-[0.5px] border-gray-200`
,
)
}
)
}
>
>
<
div
className=
"p-1"
>
<
div
className=
"p-1 max-h-50 overflow-auto"
>
<
Menu
.
Item
>
{
<
div
className=
'flex items-center px-3 h-9 hover:bg-gray-50 cursor-pointer'
>
items
.
map
(
item
=>
(
<
div
className=
'mr-2 w-5 h-5 rounded'
></
div
>
<
Menu
.
Item
key=
{
item
.
id
}
>
<
div
className=
'grow mr-2 text-sm text-gray-700'
>
LangGenius
</
div
>
<
div
<
div
className=
'text-xs font-medium text-primary-600'
>
className=
'flex items-center px-3 h-9 hover:bg-gray-50 cursor-pointer'
{
4
}
{
t
(
'common.dataSource.notion.selector.pageSelected'
)
}
onClick=
{
()
=>
onSelect
(
item
.
id
)
}
</
div
>
>
</
div
>
<
NotionIcon
</
Menu
.
Item
>
className=
'shrink-0 mr-2'
<
Menu
.
Item
>
src=
{
item
.
source_info
.
workspace_icon
}
<
div
className=
'flex items-center px-3 h-9 hover:bg-gray-50 cursor-pointer'
>
name=
{
item
.
source_info
.
workspace_name
}
<
div
className=
'mr-2 w-5 h-5 rounded'
></
div
>
/>
<
div
className=
'grow mr-2 text-sm text-gray-700'
>
LangGenius
</
div
>
<
div
className=
'grow mr-2 text-sm text-gray-700 truncate'
title=
{
item
.
source_info
.
workspace_name
}
>
{
item
.
source_info
.
workspace_name
}
</
div
>
<
div
className=
'text-xs font-medium text-primary-600'
>
<
div
className=
'shrink-0 text-xs font-medium text-primary-600'
>
{
4
}
{
t
(
'common.dataSource.notion.selector.pageSelected'
)
}
{
item
.
source_info
.
total
}
{
t
(
'common.dataSource.notion.selector.pageSelected'
)
}
</
div
>
</
div
>
</
div
>
</
div
>
</
Menu
.
Item
>
</
Menu
.
Item
>
))
}
</
div
>
</
div
>
</
Menu
.
Items
>
</
Menu
.
Items
>
</
Transition
>
</
Transition
>
...
...
web/app/components/header/index.tsx
View file @
4814d6ea
...
@@ -47,6 +47,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
...
@@ -47,6 +47,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
const
selectedSegment
=
useSelectedLayoutSegment
()
const
selectedSegment
=
useSelectedLayoutSegment
()
const
isPluginsComingSoon
=
selectedSegment
===
'plugins-coming-soon'
const
isPluginsComingSoon
=
selectedSegment
===
'plugins-coming-soon'
const
isExplore
=
selectedSegment
===
'explore'
const
isExplore
=
selectedSegment
===
'explore'
return
(
return
(
<
div
className=
{
classNames
(
<
div
className=
{
classNames
(
'sticky top-0 left-0 right-0 z-20 flex bg-gray-100 grow-0 shrink-0 basis-auto h-14'
,
'sticky top-0 left-0 right-0 z-20 flex bg-gray-100 grow-0 shrink-0 basis-auto h-14'
,
...
...
web/models/common.ts
View file @
4814d6ea
...
@@ -100,19 +100,23 @@ export type IWorkspace = {
...
@@ -100,19 +100,23 @@ export type IWorkspace = {
current
:
boolean
current
:
boolean
}
}
export
type
DataSourceNotionPage
=
{
page_icon
:
string
|
null
page_id
:
string
page_name
:
string
}
export
type
DataSourceNotionWorkspace
=
{
workspace_name
:
string
workspace_id
:
string
workspace_icon
:
string
|
null
total
:
number
pages
:
DataSourceNotionPage
[]
}
export
type
DataSourceNotion
=
{
export
type
DataSourceNotion
=
{
id
:
string
id
:
string
provider
:
string
provider
:
string
is_bound
:
boolean
is_bound
:
boolean
source_info
:
{
source_info
:
DataSourceNotionWorkspace
workspace_name
:
string
workspace_id
:
string
workspace_icon
:
string
|
null
total
:
number
pages
:
{
page_icon
:
string
|
null
page_id
:
string
page_name
:
string
}[]
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment