1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-04-27 19:05:16 +00:00

Compare commits

...

417 Commits

Author SHA1 Message Date
llj
ea580c6569
[dir view] added 'star/unstar' to the context menu of items in grid mode, and to the dropdown menu in the top toolbar for the single selected item; and other improments (#7776) 2025-04-27 17:51:40 +08:00
Aries
281b469b78
aggregate tags search results (#7775)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-04-27 13:44:59 +08:00
Michael An
f46da268ff Revert "fix import code bug"
This reverts commit 371dd4a057.
2025-04-27 11:07:32 +08:00
Huang Junxiang
5fc9b4877c
perf: read db & redis conf from .env (#7774)
* perf: read db & redis conf from .env

* perf: remove locmeme cache

* perf: remove db conf in init
2025-04-27 10:49:11 +08:00
llj
2006ff57d6 [share admin] added a missing file 2025-04-27 10:32:32 +08:00
Michael An
371dd4a057 fix import code bug 2025-04-27 09:53:43 +08:00
r350178982
09ea1ce72d Merge branch '12.0' 2025-04-25 18:16:11 +08:00
Ranjiwei
11734fe181
update (#7753)
* update

* code optimize

* Update mail.py
2025-04-25 18:12:27 +08:00
Michael An
52f71ffae1
Merge pull request #7771 from haiwen/feature/create_tag
add create tag btn on tags tree header
2025-04-25 18:11:30 +08:00
llj
66d923ccca
Share admin links batch delete (#7766)
* [Share Admin] Share Links: added 'select links and delete them in batch'

* [Share Admin] Upload Links: added 'select links and delete them in batch'

* [Share Admin] Share / Upload Links: replaced 'Cancel' & 'Delete' buttons with 'xx Selected' button & 'delete' icon

* [Share Admin] Share / Upload Links: added background color for highlighted & selected items
2025-04-25 17:07:15 +08:00
Michael An
3488ea86b0 Merge branch '12.0' 2025-04-25 16:30:34 +08:00
Michael An
29fdd70f99
update translation (#7772) 2025-04-25 16:27:40 +08:00
小强
6549f48998 update sdoc editor version 2025-04-25 14:32:31 +08:00
Michael An
04aec222ce
Merge pull request #7770 from haiwen/remove-svg-sprite-loader
remove svg-sprite-loader to fix npm audit
2025-04-25 13:43:38 +08:00
zhouwenxuan
dda7d350be add create tag btn on tags tree header 2025-04-25 10:38:09 +08:00
Michael An
726e34d47c remove svg-sprite-loader 2025-04-25 10:08:50 +08:00
Michael An
065ecb0b1f
Merge pull request #7768 from haiwen/fix-external-link-page-do-not-show-migrate-tag-info
fix external link page do not show migrate tag info
2025-04-24 21:59:40 +08:00
Michael An
0b00f08e3f fix external link page do not show migrate tag info 2025-04-24 21:53:06 +08:00
欢乐马
b43ad4132b
seafile.sh IS_PRO_VERSION (#7765) 2025-04-24 12:15:57 +08:00
Aries
2ec7bc4da9
Optimize/search filters (#7761)
* optimize ui

* optimize filters

* update date filter

* optimize

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-04-23 16:11:03 +08:00
Michael An
75cde31370
Merge pull request #7751 from haiwen/upload-replace-fixup
[dir view] fixed 'upload & replace a file'(so that the related upload…
2025-04-23 15:51:30 +08:00
Michael An
bffdcdfa7e
Merge pull request #7764 from haiwen/fix-svg-package-npm-audit
fix npm audit
2025-04-23 15:21:42 +08:00
Michael An
f2952cb6fb fix npm audit 2025-04-23 15:18:03 +08:00
小强
f10c51c461 fix exdraw bug 2025-04-23 14:58:52 +08:00
awu0403
c6afca8eba
fix link invite group member (#7763)
* fix link invite group member

* Update group-op-menu.js

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-04-23 14:20:44 +08:00
zhichaona
8e2be63d3f
Add excl draw module 4 (#7740)
* add exdraw apis

* temporarily submit

* rebase exdraw_apis

* Interacting with the exdraw-server

* update

* optimize code

* optimize code

---------

Co-authored-by: ‘JoinTyang’ <yangtong1009@163.com>
Co-authored-by: 小强 <shuntian@xiaoqiangdeMacBook-Pro.local>
2025-04-23 14:10:11 +08:00
Michael An
444e690255
Merge pull request #7754 from haiwen/dependabot/npm_and_yarn/frontend/http-proxy-middleware-2.0.9
Bump http-proxy-middleware from 2.0.7 to 2.0.9 in /frontend
2025-04-23 12:07:15 +08:00
Michael An
0cd14ab806
Merge pull request #7759 from haiwen/windows-ui
fixed windows-ui
2025-04-23 11:58:09 +08:00
Ranjiwei
1a4d7038a9
Update lib-sub-folder-set-user-permission-dialog.js (#7762) 2025-04-23 11:43:52 +08:00
llj
bfc2812a4f
[login] 2fa: redesigned 2 pages(2fa with a token; 2fa with a backup code) (#7760) 2025-04-22 21:23:11 +08:00
llj
1ba5c44839
[dir view] the top toolbar for 1 selected dirent: display 'share' icon (remove 'share' from the downdown menu), replaced the 'vertical dots' icon with a 'horizontal dots' icon; other improvements (#7758) 2025-04-22 21:22:29 +08:00
小强
45c9411973 update sdoc translate 2025-04-22 16:29:26 +08:00
zhichaona
36af8862b1 fixed windows-ui 2025-04-22 16:28:37 +08:00
小强
145f2d17db update sdoc editor version 2025-04-22 16:07:01 +08:00
小强
e68a442a3e update sdoc editor version 2025-04-22 16:06:05 +08:00
Ranjiwei
6874c2a40c
Update onlyoffice_file_view_react.html (#7757) 2025-04-22 16:00:18 +08:00
欢乐马
52e020e227
13.0.2 sql (#7756) 2025-04-22 10:49:22 +08:00
dependabot[bot]
d7e4866fc0
Bump http-proxy-middleware from 2.0.7 to 2.0.9 in /frontend
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.7 to 2.0.9.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.7...v2.0.9)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-version: 2.0.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-22 01:59:51 +00:00
Michael An
7ff4b52005
basic file support comment (#7731)
* basic file support comment

* 01 add init loading icon

* delete useless comment

* 02 delete comment tip

* update api validation

* 03 update API params

* 04 delete useless api

* 05 remove read all notification

* 06 change comment and reply permission

* 07 change docUuid to fileUuid

---------

Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
2025-04-21 21:33:13 +08:00
Aries
1cf26c3d2c
list repo tags regardless of whether metadata tag is enabled (#7752)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-04-21 18:25:43 +08:00
llj
84e763cddf [dir view] fixed 'upload & replace a file'(so that the related upload data can be displayed in the popover) 2025-04-21 17:32:27 +08:00
Aries
63f51d6d2a
Feature/search filters controller (#7739)
* add search filter controller

* update custom date

* optimize ui

* update bg color

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-04-21 15:50:06 +08:00
awu0403
8cc5815107
Send notification to cloud user (#6415)
* send subscription expire notification

* optimize code

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-04-21 13:30:35 +08:00
Michael An
0b8aa00f4d
Merge pull request #7743 from haiwen/fix/dirent_selection
fix dirent state in grid view
2025-04-21 10:42:14 +08:00
Michael An
4c3cc1ae19 fix code format 2025-04-21 10:34:49 +08:00
zhouwenxuan
920b7fe430 fix dirent state in grid view 2025-04-21 10:27:09 +08:00
awu0403
e11dd9e34c
fix special folder judgement (#7749)
Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-04-20 09:18:54 +08:00
awu0403
f24516e88a
Add deactivation option (#7626)
* sql delete share relation

* update rpc remove share

* update

* use sql remove share

* code optimize

* code optimize

* update selector

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
2025-04-20 08:54:41 +08:00
awu0403
38c6ea36ae
Optimize dir size (#7732)
* add dir size

* update

* use sql query file md

* optimize sql

* update sql

* exclude special dir

* update

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-04-20 08:53:12 +08:00
lian
e20902279e set org setting is_active default True 2025-04-19 16:07:07 +08:00
JoinTyang
62f31eee77
fix update records bug (#7747) 2025-04-19 14:34:02 +08:00
lian
cb05c2390e
add org_created_callback (#7746) 2025-04-19 14:32:51 +08:00
Michael An
de09b014a0
Merge pull request #7744 from haiwen/optimize/selection_ui_on_safari
disable text selection in grid view
2025-04-19 09:43:05 +08:00
Michael An
d733bbccc8
Merge pull request #7745 from haiwen/fix/selection_state_reset_after_switch_mode
reset dirent selection state after switch mode
2025-04-19 09:38:31 +08:00
zhouwenxuan
8cb5ecf83c reset dirent selection state after switch mode 2025-04-18 17:41:03 +08:00
zhouwenxuan
b3602c6fa5 disable text selection in grid view 2025-04-18 17:32:52 +08:00
Michael An
d7c3b459d2
Merge pull request #7741 from haiwen/optimize/enable_ocr_configuration
add ENABLE_SEAFILE_OCR, false by default
2025-04-17 17:25:12 +08:00
zhouwenxuan
d988c3f0cb optimize 2025-04-17 16:54:03 +08:00
zhichaona
a524757a92
set exdraw favicon (#7742) 2025-04-17 10:31:17 +08:00
zhouwenxuan
a3df5ddd0e add ENABLE_OCR, false by default 2025-04-16 17:08:15 +08:00
JoinTyang
b398c493c2
fix tag invalid bug (#7738) 2025-04-16 11:28:47 +08:00
Michael An
759189ae78 Merge branch '12.0' 2025-04-15 15:25:11 +08:00
Michael An
ca4aa8b0cb delete error translation 2025-04-15 15:24:47 +08:00
Michael An
344cd865b2 Merge branch '12.0' 2025-04-15 14:19:50 +08:00
Michael An
e157f8675e 12.0 update translation (#7737) 2025-04-15 14:16:22 +08:00
Michael An
ef8eb9137f
12.0 update translation (#7737) 2025-04-15 14:01:02 +08:00
Aries
c23a153818
Feature/show tags in search dialog (#7727)
* show related tags

* optimize

* fix eslint warning

* change searched tags background

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
Co-authored-by: Michael An <1822852997@qq.com>
2025-04-15 13:52:37 +08:00
Michael An
31e0b24e07
12.0 department set quota (#7736)
* 01 system admin department set quota

* 02 org admin department set quota
2025-04-15 13:46:17 +08:00
小强
da86a8e1b0 Fix reg compatibility issues 2025-04-15 11:02:42 +08:00
Michael An
1ec93e6f5a
Merge pull request #7735 from haiwen/fix-file-activity-page-UI
fix activity page scroll and css format
2025-04-14 17:30:01 +08:00
Michael An
6e6f49beed fix activity page scroll and css format 2025-04-14 17:10:58 +08:00
Michael An
1f77db68e5
Merge pull request #7733 from haiwen/fix-search-in-tags-page
fix search folder in tag view
2025-04-14 16:45:30 +08:00
Michael An
9249e4da17 fix search folder in tag view 2025-04-14 15:44:55 +08:00
Michael An
7a7079ed48
Merge pull request #7715 from haiwen/fix/file_tree_update_after_moving
update tree nodes after move files
2025-04-14 14:43:04 +08:00
Michael An
39f12f1279
Merge pull request #7730 from haiwen/optimize/libcontent_view_unnecessary_render
remove redundant path reset, remove unsafe lifecycle method
2025-04-14 14:29:20 +08:00
JoinTyang
59c719e64f
Merge pull request #7705 from haiwen/face_recognition_menu
face recognition menu
2025-04-14 11:45:58 +08:00
zheng.shen
17a4f2c637 update 2025-04-14 11:40:27 +08:00
lian
19d555880b
support view/edit csv file via onlyoffice (#7725)
* support view/edit csv file via onlyoffice

* update

* update
2025-04-11 20:11:22 +08:00
zheng.shen
8d837c8195 update 2025-04-11 17:20:59 +08:00
zhouwenxuan
8a18ada09a remove redundant path reset, remove unsafe lifecycle method 2025-04-11 17:03:28 +08:00
Michael An
8ebf4e7225
Merge pull request #7709 from haiwen/fix/file_tree
fix tree data update
2025-04-11 14:16:34 +08:00
Michael An
034f1e2a04
Merge pull request #7710 from haiwen/optimize/dropdown_menu
optimize dropdown menu open direction
2025-04-11 13:45:15 +08:00
Aries
c9984a8319
fix column data setting ui (#7728)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-04-11 13:05:09 +08:00
小强
05f5d13c20 update sdoc version 2025-04-11 11:20:38 +08:00
awu0403
6b51e54596
System metrics (#7700)
* add metric ui

* Update statistic-metrics.js

* handle metrics

* optimize ui

* update

* optimize ui

* update ui

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-04-11 11:09:25 +08:00
Michael An
1134469495
Remove deduplicated css (#7726)
* remove deduplicated css

* fix code warnings
2025-04-11 11:06:49 +08:00
zhouwenxuan
466e8f3a40 fix repeated build tree 2025-04-11 10:03:29 +08:00
zhouwenxuan
885b60f566 fix tree data update failed when switch repo by click searched folder 2025-04-11 10:03:29 +08:00
JoinTyang
272010d55f
optimize shared repos search (#7722)
* optimize shared repo search

* Update utils.py

---------

Co-authored-by: Daniel Pan <freeplant@gmail.com>
2025-04-10 17:44:23 +08:00
小强
7fb3b29e3a update sdoc editor version 2025-04-10 14:13:42 +08:00
awu0403
8e36f412da
Optimize fileter log (#7669)
* filter log v1

* optimize log user selector

* optimize ui and filter repo

* optimize ui of file update and permission

* admin filter completed

* optimize selector component

* optimize lint

* optimize code

* optimize filter

* update

* add filter group audit

* update parameters

* Update log-user-selector.js

* Update log-filter.css

* update var name

* update func name

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
2025-04-10 13:55:15 +08:00
Michael An
efb1ac8286
Merge pull request #7724 from haiwen/fix-create-view-in-folder-bug
fix create view in folder bug
2025-04-09 23:00:33 +08:00
Michael An
c3fab488f6 01 fix create view in folder bug 2025-04-09 22:50:14 +08:00
小强
b599bed239 optimize code 2025-04-09 18:20:45 +08:00
Michael An
72f7e68bdc
fix readonly repo markdown editor (#7718) 2025-04-09 17:30:08 +08:00
Aries
3ec8c646b1
Optimize/seasearch filters (#7723)
* update seasearch filters

* seasearch support filter by owner and search_filename_only

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-04-09 17:02:16 +08:00
Ranjiwei
951698555d
Merge pull request #7701 from haiwen/org-register
send activity email when org register
2025-04-09 15:43:44 +08:00
awu0403
618f75ab13
Fix real lock (#7721)
* update

* fix get dirRouter

* Update lib-content-view.js

* Update lib-content-view.js

* Update lib-content-view.js

* update name

* Update lib-content-view.js

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-04-09 14:59:58 +08:00
杨顺强
2e5b2797b3
add excalidraw viewer (#7719)
Co-authored-by: 小强 <shuntian@xiaoqiangdeMacBook-Pro.local>
2025-04-09 11:16:01 +08:00
r350178982
2be71dd00b Update models.py 2025-04-08 17:05:48 +08:00
Michael An
1be01e5186
Change generate tags UI (#7712)
* 01 change header icon class

* 02 change tags UI

* 03 change modal header title
2025-04-08 16:47:35 +08:00
杨国璇
e67fc4a3d9
fix: metadata switch classname (#7717)
Co-authored-by: 杨国璇 <ygx@Hello-word.local>
2025-04-08 16:35:47 +08:00
Aries
cb69c6662e
Feature/filters in search dialog (#7681)
* search results filters

* hide filters for seasearch

* optimize

* filter by suffix

* fix dropdown menu z-index

* update ui

* optimise css

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
Co-authored-by: Michael An <1822852997@qq.com>
2025-04-08 15:46:58 +08:00
杨国璇
53bedae485
feat: metadata status callback (#7716)
* feat: metadata status callback

* feat: optimzie code

---------

Co-authored-by: 杨国璇 <ygx@Hello-word.local>
2025-04-08 15:00:02 +08:00
Lewis
a865aecb6f
Merge pull request #7695 from haiwen/change-notice-icon
change notice close icon style
2025-04-08 14:58:07 +08:00
zhouwenxuan
ef4fbafa04 update tree nodes after move files 2025-04-08 14:32:29 +08:00
欢乐马
a1686622b3
Merge pull request #7714 from haiwen/12.0-add-forwarder-header-in-gunicorn
Update setup-seafile-mysql.py
2025-04-08 14:06:13 +08:00
llj
cf262f09db
[group] added full operation menus for all the department/group items… (#7706)
* [group] added full operation menus for all the department/group items in 'Files' page; fixed & improved all the operations

* remove useless state

* fix test lib

---------

Co-authored-by: Michael An <1822852997@qq.com>
2025-04-08 13:47:28 +08:00
r350178982
ee61bbd7c5 Update setup-seafile-mysql.py 2025-04-08 13:21:12 +08:00
Aries
b28e97970f
optimize view tree render (#7713)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-04-08 11:21:36 +08:00
Ranjiwei
baddc4bad7
Update single-selector.js (#7707) 2025-04-08 11:20:42 +08:00
Aries
641eb1fca5
check tags status before initialize repo tags (#7711)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-04-08 10:40:10 +08:00
小强
dfa86ebc45 update sdoc editor version 2025-04-07 17:28:54 +08:00
Michael An
ee09f7b0f8
Fix search tags not by RGB color (#7708)
* remove search tag by RGB color

* remove search tag by tag RGB color
2025-04-07 16:04:14 +08:00
zhouwenxuan
538bf10d18 optimize dropdown menu open direction 2025-04-07 15:52:37 +08:00
lian
04f948f7e4 send activity email when org register 2025-04-07 11:20:07 +08:00
zheng.shen
b58e48db0c update 2025-04-03 18:06:21 +08:00
zheng.shen
b27bbf6cce face recognition menu 2025-04-03 16:29:42 +08:00
Ranjiwei
09581a961a
Merge pull request #7026 from haiwen/lock-notification
Lock notification
2025-04-03 13:23:59 +08:00
Michael An
247a5b06ae change format 2025-04-03 09:28:42 +08:00
孙永强
6d32b4409b handle repo-update 2025-04-03 09:28:41 +08:00
孙永强
b27198bf02 update 2025-04-03 09:28:41 +08:00
孙永强
acd1a4a957 listen notification
optimize code

Update user-api.js

optimize code

optimize code

Update lib-content-view.js

Update lib-content-view.js

remove-userless-code

update settings

optimize cur code

add max number of reconnections
2025-04-03 09:28:36 +08:00
Michael An
56d4ebc785
migrate tags UI and delete old tags (#7699)
* basic codes

remove useless

* remove useless

* remove useless codes

* remove useless codes
2025-04-02 16:04:39 +08:00
小强
894679436c update sdoc-editor version 2025-04-02 15:59:12 +08:00
awu0403
073af84027
Add wiki freeze (#7673)
* freeze wiki page

* optimize

* optimize

* update

* update

* update

* update

* update

* Update wiki2.py

* update ui

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
2025-04-02 13:05:17 +08:00
JoinTyang
cc99ce2e90
seasearch support fulltext search (#7696) 2025-04-02 11:47:06 +08:00
杨国璇
e332873d8e
Merge pull request #7697 from haiwen/fix-metadata-status
fix: metadata status
2025-04-01 18:36:10 +08:00
杨国璇
75460d1d7c fix: metadata status 2025-04-01 17:30:16 +08:00
lian
792135a224
remove share/upload links when user is deleted (#7694) 2025-04-01 16:54:25 +08:00
Michael An
f6ede9c8a7 change notice close icon style 2025-04-01 14:40:27 +08:00
Michael An
6614fd20a6
change register page style (#7693)
* change register page style

* remove useless file
2025-04-01 12:23:39 +08:00
awu0403
efe9ecce29
remove xmind (#7688)
Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-03-31 18:28:47 +08:00
Michael An
d58584e0d7
hide AI icon when SeafileAI is false (#7692)
* hide AI icon when SeafileAI is false

* fix test env
2025-03-31 18:27:31 +08:00
Ranjiwei
13098287d3
Update share_links.py (#7691) 2025-03-31 16:20:48 +08:00
Huang Junxiang
6d9d952079
revert: Python 3.8 compatibility (#7690)
* revert: Python 3.8 compatibility

* update(settings.py): default value for CACHE

* fix(settings.py): fetch redis cfg locations from env

* update requirements

* feat(gitcli::test.yml): support redis test

* feat(gitcli::test.yml): support redis test

* style(gitcli::test.yml): trailing blank line
2025-03-31 15:50:11 +08:00
Michael An
dd3f25e216
remove useless lib (#7689) 2025-03-31 15:01:19 +08:00
lian
c39c7c1f34
fix Uncontrolled data used in path expression (#7686)
* fix Uncontrolled data used in path expression

* Potential fix for code scanning alert no. 195: Uncontrolled data used in path expression

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Potential fix for code scanning alert no. 196: Uncontrolled data used in path expression

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-03-31 13:48:08 +08:00
zhichaona
700e933863
exdraw icon (#7687) 2025-03-31 11:48:13 +08:00
Huang Junxiang
71da2e685e
perf: read conf from env (#7680) 2025-03-31 10:50:25 +08:00
Ranjiwei
4ef9496557
update (#7682) 2025-03-31 09:53:39 +08:00
Michael An
2ed3b81934
Merge pull request #7684 from haiwen/fix/delete_image_failed
fix delete image failed when previewing in tag files view
2025-03-29 17:33:53 +08:00
Aries
8eb1c65ae3
fix edit detail failed in image previewer (#7685)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-29 17:27:24 +08:00
小强
da16173f35 update sdoc editor version 2025-03-29 16:07:39 +08:00
zhouwenxuan
a4835a9d7b fix delete image failed when previewing in tag files view 2025-03-29 15:57:34 +08:00
JoinTyang
beef890a47
fix file type (#7679) 2025-03-29 13:59:05 +08:00
JoinTyang
7723ef6fb7
add exdraw (#7678) 2025-03-29 13:40:23 +08:00
seafile-dev
5eb303c76b
fix Use of a broken or weak cryptographic hashing (#7671)
Co-authored-by: lian <imwhatiam123@gmail.com>
2025-03-29 13:16:01 +08:00
llj
c8026ddb6c
2fa redesign (#7677)
* ['enable 2fa' page] redesigned it

* ['2fa - backup tokens' page] redesigned it.

* ['disable 2fa' page] redesigned it
2025-03-29 13:14:29 +08:00
llj
872ae595b8
[shared dir view] display the loading icon in the content area when the users visit a folder (#7676) 2025-03-29 11:07:08 +08:00
Aries
796600eef6
collapse tag node by default (#7675)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-29 10:34:10 +08:00
小强
281a81cbd2 update sdoc translate 2025-03-28 18:01:13 +08:00
awu0403
ebe1c54153
Add group audit log 13 (#7661)
* add group audit log

* Update mysql.sql

* optimize parameters

* update

* update

* update

* optimize

* code optimize

* update

* Update operation-logs.js

* update

* Update models.py

* update

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
2025-03-28 17:51:29 +08:00
Guodong SU
29c8c12fa8
update newly created wiki page name (#7666) 2025-03-28 16:27:19 +08:00
小强
f7aaa0bff4 update sdoc editor version 2025-03-28 16:01:57 +08:00
欢乐马
f06981267d
rm CCNET_CONF_DIR (#7670) 2025-03-28 14:40:42 +08:00
JoinTyang
30036bf83f
fix invisable permission bug (#7672) 2025-03-28 14:27:15 +08:00
Aries
005ddb4dca
optimize tag editor ui (#7647)
* optimize tag editor ui

* optimize selection by up/down key

* optimize popover position

* fix searched tree nodes folding

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-28 11:45:17 +08:00
Aries
0abb343b4b
set fallback video thumbnail (#7668)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-27 16:26:18 +08:00
llj
0666b7a303
[system admin / users] redesigned the 'user filter' toolbar for the 'Database' tab (#7667) 2025-03-27 14:15:45 +08:00
zhichaona
3ec456fff5
time-base saving & toastTips (#7662)
* time-base saving & toastTips

* refactor hasChanged

---------

Co-authored-by: First <first@FirstdeMacBook-Pro.local>
2025-03-26 10:25:36 +08:00
Michael An
055bd575b9
change dirent detail value style (#7664) 2025-03-25 16:14:35 +08:00
Michael An
28fb4f4887
Change library setting entry permission (#7663)
* change lib setting AI entry

* change file name
2025-03-25 14:09:07 +08:00
Michael An
8d4377f85b Merge branch '12.0' 2025-03-24 17:33:12 +08:00
Michael An
18c72b9391
01 remove sort in share links page (#7660) 2025-03-24 16:50:07 +08:00
Ranjiwei
e872b4eff8
Merge pull request #7644 from haiwen/code-scan-db-api
fix "SQL query built from user-controlled sources"
2025-03-24 16:45:53 +08:00
r350178982
27c5d0294f remove-ch-characters 2025-03-24 16:31:40 +08:00
lian
5b63ef83f0 update 2025-03-24 16:31:40 +08:00
lian
db0e17b645 fix "SQL query built from user-controlled sources" 2025-03-24 16:31:40 +08:00
Michael An
2152dca689
gif image do not support rotate (#7641)
* fix image rotate file type

* fix code warning
2025-03-24 14:38:36 +08:00
lian
7b60cc38aa remove office convert test 2025-03-24 14:34:47 +08:00
lian
213927e1a7
fix Polynomial regular expression used on uncontrolled data (#7656)
* fix Polynomial regular expression used on uncontrolled data

* remove office convert code
2025-03-24 14:26:33 +08:00
zhichaona
ff7fd0f0d5
Add excl draw module 2 (#7658)
* init exceldraw module

* excalidraw demo

* i18n fixed bug

* exceldraw change to excalidraw

* lang setting

---------

Co-authored-by: 杨顺强 <978987373@qq.com>
Co-authored-by: First <first@FirstdeMacBook-Pro.local>
2025-03-24 14:11:58 +08:00
lian
ef9be3fed1 add new migrations files 2025-03-24 14:02:20 +08:00
JoinTyang
7d46c7aaa2
optimize wiki search permission (#7659) 2025-03-24 13:43:12 +08:00
小强
5e456c569a update sdoc version 2025-03-24 11:51:06 +08:00
Ranjiwei
75034bd9f1
Merge pull request #7655 from haiwen/code-scan-regex
fix Regular expression injection
2025-03-24 11:09:42 +08:00
awu0403
8151f7cf1c
fix clear text logging (#7640)
* fix clear text logging

* update

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-03-24 11:01:49 +08:00
Ranjiwei
3c9394ced4
Merge pull request #7619 from haiwen/fix-Information-exposure-through-an-exception
fix Information exposure through an exception
2025-03-24 10:02:53 +08:00
Michael An
61cfae3d08
Merge pull request #7648 from haiwen/fix/path_navigation
reset state after navigation
2025-03-23 09:47:03 +08:00
Michael An
1841d799a2
Merge pull request #7650 from haiwen/feature/sort_tag_files
Feature/sort tag files
2025-03-23 09:41:50 +08:00
Michael An
b93e47b606 fix span key 2025-03-23 09:40:39 +08:00
zhouwenxuan
41a12fa90e optimize 2025-03-23 09:33:42 +08:00
zhouwenxuan
d27ea6be9f sort tag files 2025-03-23 09:33:42 +08:00
Michael An
e9c61f2bec
Merge pull request #7653 from haiwen/privacy-policy
update privacy-policy UI on login page
2025-03-21 18:22:55 +08:00
Michael An
baa144cf80 change font color 2025-03-21 18:21:56 +08:00
llj
cacef99651
[library decryption dialog] fixed the 'close' icon after the upgrade of seafile-ui.css (#7654) 2025-03-21 18:00:55 +08:00
lian
03e326d56f fix Regular expression injection 2025-03-21 17:44:56 +08:00
杨顺强
67e78e76e3 update dev script 2025-03-21 17:34:17 +08:00
lian
a998903f52 update 2025-03-21 17:05:39 +08:00
杨顺强
39aac08f3d update sdoc editor version 2025-03-21 16:29:33 +08:00
lian
0aa2d11f36 update privacy-policy UI on login page 2025-03-21 10:53:18 +08:00
欢乐马
592354b3cf
13.0 repo_metadata sql (#7652) 2025-03-20 18:36:34 +08:00
llj
db5a8b0695
[user settings] fixed 'Set WebDAV Password' dialog (#7651) 2025-03-20 17:42:28 +08:00
llj
34992f7ee7
[org admin / settings] fixed 'user default quota' setting and other 'input' setting items('org name', 'file ext white list'...) (#7649) 2025-03-20 17:12:44 +08:00
杨顺强
7365db5295 update sdoc editor version 2025-03-20 16:54:26 +08:00
zhouwenxuan
ec9e513699 reset state after navigation 2025-03-20 14:33:58 +08:00
Ranjiwei
fa79c2b3a3
Update internal_api.py (#7642)
* Update internal_api.py

* Update internal_api.py

* Update internal_api.py
2025-03-19 18:58:05 +08:00
lian
61426b04d9
fix Inclusion of functionality from an untrusted source (#7645) 2025-03-19 18:08:24 +08:00
llj
9a8b731780
[UI] fixed the color of .input-group-text's left border (#7643) 2025-03-19 16:34:53 +08:00
杨顺强
f6f39685a7 update sdoc editor version 2025-03-19 12:00:51 +08:00
欢乐马
a25bb24b05
github actions permission (#7624) 2025-03-18 18:56:30 +08:00
Aries
abf09b4593
show video thumbnail (#7639)
* show video thumbnail

* check preview permission

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-18 17:58:43 +08:00
Aries
618e25d1ab
Feature/all tags sort (#7618)
* sort menu

* sort tag tree

* optimize

* optimize

* update codes

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
Co-authored-by: renjie-run <rj.aiyayao@gmail.com>
2025-03-18 17:40:02 +08:00
Michael An
88de887f82 update sdoc editor version 2025-03-18 15:11:55 +08:00
Michael An
4cc3250c4a
Change metadata tag language setting style (#7636)
* change metadata lang setting

* change tip text
2025-03-18 14:57:58 +08:00
Michael An
742d7eb311 remove useless socket.io 2025-03-18 11:59:37 +08:00
Michael An
34b63a771d
Merge pull request #7637 from haiwen/fix-audit
fix npm audit
2025-03-18 11:49:34 +08:00
Michael An
0dda9864d0 fix npm audit 2025-03-18 11:45:08 +08:00
Michael An
0b698bf13c
Add metadata kanban collapsed tip (#7628)
* 01 change tip color to # 666

* 02 show tips when Kanban is collapsed

* 03 fix warning
2025-03-18 10:09:28 +08:00
llj
63c3b36b80
[pdf file view] fixup: make the top-right toolbar restore usable (#7634) 2025-03-17 22:16:33 +08:00
杨顺强
5c9edac317 update sdoc editor version 2025-03-17 21:14:41 +08:00
杨顺强
020a04d6ba update sdoc version 2025-03-17 20:38:36 +08:00
欢乐马
20866e43cc
update 13.0 sql (#7633) 2025-03-17 17:57:14 +08:00
llj
9263a445c5
[dir view] 'move', 'copy' dialogs: fixed the split line of the left and right panels after the upgrade of seafile-ui.css (#7632) 2025-03-17 17:49:46 +08:00
Michael An
acfc971ddd Merge branch '12.0' 2025-03-17 16:36:44 +08:00
llj
2c57086489
[PDF viewer] fixed the decryption dialog after the upgrade of seafile-ui.css(bootstrap v4 to v5) (#7630) 2025-03-17 15:47:43 +08:00
Michael An
61ae1a34fb
fix wiki sdoc outline style (#7629) 2025-03-17 15:04:41 +08:00
杨国璇
ae9e109c46
fix: reg (#7620)
Co-authored-by: 杨国璇 <ygx@Hello-word.local>
2025-03-17 12:50:50 +08:00
杨国璇
339a149c0c
fix: location href url (#7621)
Co-authored-by: 杨国璇 <ygx@Hello-word.local>
2025-03-17 12:49:40 +08:00
杨国璇
a53d4978d1
fix: password random (#7615)
* fix: password random

* fix: code

---------

Co-authored-by: 杨国璇 <ygx@Hello-word.local>
2025-03-17 12:31:33 +08:00
Ranjiwei
de14104af6
Update zip-download-dialog.js (#7625) 2025-03-17 11:56:33 +08:00
Michael An
e60eb83c93
Add repo setting tip when disable Metadata (#7623)
* Add repo setting tip when disable Metadata

* can not use button when disable metadata
2025-03-17 11:17:22 +08:00
Michael An
f44e6eea7d
Merge pull request #7614 from haiwen/dependabot/npm_and_yarn/frontend/babel/runtime-7.26.10
Bump @babel/runtime from 7.26.0 to 7.26.10 in /frontend
2025-03-17 11:04:43 +08:00
llj
19a5de3328
V5 rtl fixup (#7622)
* [system admin, org admin] departments: fixup for the 'New Department' button after the upgrade of seafile-ui.css(bootstrap v4 to v5)

* [wiki] removed unused code

* [Activities] fixup for mobile after the upgrade of seafile-ui.css(bootstrap v4 to v5)

* [repo snapshot] fixed the split line after the upgrade of seafile-ui.css(bootstrap v4 to v5)

* [system admin, org admin] statistic: fixed & improved the 'days' selecting toolbar
2025-03-15 23:41:14 +08:00
孙永强
3f0228e829 fix 2025-03-15 14:54:00 +08:00
欢乐马
9e2d9c49bb
13.0-sql (#7617) 2025-03-15 12:09:31 +08:00
dependabot[bot]
ca5225f6cc
Bump @babel/runtime from 7.26.0 to 7.26.10 in /frontend
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.26.0 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-15 02:28:20 +00:00
Michael An
f1050952dd
Fix mobile UI in wiki trash dialog and share dialog (#7612)
* 01 change mobile wiki trash dialog

* 02 share dialog support mobile use

* 03 fix share to group click outside
2025-03-15 10:02:56 +08:00
llj
f46070e76d
['library folder permission' dialog] redesigned the 'add folder' panel (#7613)
- added a title, a 'back' icon, and a 'submit' button to the top
- removed the 'cancel' & 'submit' buttons in the bottom
- added gaps above & below the search input
2025-03-14 17:35:41 +08:00
llj
94553e0ef1
Mobile item op menu fixup (#7590)
* [my libs] fixed the operation menu items for library items in mobile after
reactstrap was upgraded from v8 to v9

- after the upgrade for reacstrap, the operation menu items fail to work

* [shared dir view] fixed the operation menu items in mobile

* [new component] added a new component 'MobileItemMenu' for items' operation menu in mobile

* [user settings] linked devices: fixed the operation menus in mobile

* [library content view] fixed the operation menus in mobile

* [department/group repo list, shared with all] fixed the operation menus in mobile

* [shared with me] fixed the operation menus in mobile

* [Favorites] fixed the operation menus in mobile

* [share admin / libraries] fixed the operation menus in mobile

* [share admin / folders] fixed the operation menus in mobile

* [share admin / share links] fixed the operation menus in mobile

* [share admin / upload links] fixed the operation menus in mobile

* [linked devices] fixed the operation menus in mobile

* [invite guest] fixed the operation menus in mobile

* [repo trash dialog] fixed the operation menus in mobile
2025-03-14 17:09:04 +08:00
llj
83be683b99
Misc UI fixup (#7610)
* [user settings] fixup for 'set webdav password'

* ['New Group' dialog] fixup
2025-03-14 17:08:21 +08:00
Aries
de2032e59c
fix record not found in image dialog (#7609)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-14 16:09:14 +08:00
Aries
0c29734c5f
Fix/record not found (#7606)
* fix path

* fix record not found after rename node

* fix path update

* fix rename dialog display

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-14 10:31:04 +08:00
杨顺强
cb074ea304 update sdoc-editor version 2025-03-13 17:53:34 +08:00
Michael An
b34596accf
Merge pull request #7607 from haiwen/fix-form-and-close-UI
Fix form and close icon UI
2025-03-13 17:45:01 +08:00
Michael An
47852c1b4a update timestamp 2025-03-13 17:39:53 +08:00
Michael An
c12696b7c5 fix form and close UI 2025-03-13 17:37:20 +08:00
shenzheng-1
796d2c3144
Support ai instruction (#7595)
* support ai instruction

* update

* update

---------

Co-authored-by: zheng.shen <zheng.shen@seafile.com>
2025-03-13 17:31:16 +08:00
Michael An
d4af41cbce
Merge pull request #7602 from haiwen/fix-UI
fix table and nav UI after seafile-ui.css update
2025-03-13 11:45:38 +08:00
Michael An
40bd6b004b fix table and nav UI 2025-03-13 11:30:23 +08:00
Aries
68fa0a311e
optimize avatar in detail (#7601)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-12 23:02:37 +08:00
Michael An
5f412d7046 fix warnings 2025-03-12 20:17:00 +08:00
Michael An
db178150ae
Merge pull request #7587 from haiwen/13.0-use-new-seafile-ui
update seafile-ui.css
2025-03-12 18:23:25 +08:00
llj
d8156bbd25
[publish wiki] don't close the dialog after publishing the wiki (#7600) 2025-03-12 18:23:14 +08:00
Aries
bddbf3663b
optimize folder details display (#7596)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-12 17:46:36 +08:00
Aries
8dc9d0cdd1
update tag icon (#7598)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-12 17:46:02 +08:00
llj
8f99a7a651
[wikis] redesigned the 'No Wikis' UI(added a 'Add Wiki' button) (#7599) 2025-03-12 17:45:23 +08:00
Michael An
571aad3f53 add timestamp 2025-03-12 15:44:45 +08:00
Michael An
e33d14cbf7 update seafile-ui version 2025-03-12 15:41:38 +08:00
Michael An
bd92e7e5ef change modal header style 2025-03-12 15:41:38 +08:00
llj
cc3cb2f7a8
[system admin, org admin] departments: modified UI details for the 'New Department' button (#7594) 2025-03-12 15:38:29 +08:00
Aries
a2ee1fede2
Optimize/all tags view toolbar (#7593)
* all tags view toolbar

* optimize

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-12 13:55:41 +08:00
杨国璇
d58878ec39
Merge pull request #7592 from haiwen/fix-metadata-modify-record-bug
Fix metadata modify record bug
2025-03-12 13:31:51 +08:00
Michael An
890183fe6c
Merge pull request #7591 from haiwen/fix/dropdown_menu_position
fix dropdown menu position
2025-03-12 12:06:35 +08:00
Aries
b9be45d151
display location column (#7569)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-12 12:01:31 +08:00
Michael An
75401f88f6 remove warnings 2025-03-12 11:46:41 +08:00
Michael An
752622f35e 02 fix metadata table view updateRecords to modifyRecords bug 2025-03-12 11:37:45 +08:00
Michael An
43cd67f8b1 01 fix tagsData type warning 2025-03-12 11:33:14 +08:00
zhouwenxuan
9bd4f15d94 fix dropdown menu position 2025-03-12 11:33:05 +08:00
杨顺强
a205d62cd7 update sdoc editor version 2025-03-12 10:45:01 +08:00
Michael An
c10575b730
Merge pull request #7560 from haiwen/fix/column_dropdown_menu
fix column type dropdown menu
2025-03-11 18:04:48 +08:00
杨国璇
79fae81959
Merge pull request #7588 from haiwen/fix-sf-editor-x-icon
Fix sf editor x icon
2025-03-11 16:38:13 +08:00
杨国璇
0e492fad2a feat: update sdoc 2025-03-11 16:31:33 +08:00
杨国璇
63830e1edc fix: sf editor x icon 2025-03-11 16:19:45 +08:00
杨顺强
33aa6d3c6b update deps 2025-03-11 15:03:25 +08:00
欢乐马
a58f877b94
github actions ignore repo_metadata (#7583)
* github actions ignore repo_metadata

* fix test

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-03-11 11:41:51 +08:00
JoinTyang
f664bed3a1
Merge pull request #7544 from haiwen/fix-lat-lng
Fix lat lng
2025-03-11 11:06:09 +08:00
awu0403
8ba1eedb47
Add org last activity time (#7575)
* update

* optimize code

* update ui

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-03-11 11:01:40 +08:00
孙永强
5fe0275432 fix lat lng
Update coord-transform.js
2025-03-11 09:55:45 +08:00
杨国璇
0e7cc9f3c5
Merge pull request #7582 from haiwen/fix-wgs84_to_gcj02
fix: wgs84_to_gcj02
2025-03-10 18:34:44 +08:00
杨国璇
e4b9430ce1 fix: wgs84_to_gcj02 2025-03-10 18:33:51 +08:00
Ranjiwei
60d48c1767
Merge pull request #7580 from haiwen/fix-file-send-updates-bug
Update send_file_updates.py
2025-03-10 18:23:09 +08:00
r350178982
102273b5d5 Update send_file_updates.py 2025-03-10 18:19:19 +08:00
Michael An
0e2627cde3 Merge branch 'master' into 13.0 2025-03-10 18:03:51 +08:00
Michael An
eabb0faed5
13.0 change dir operation dropdown menu (#7579) 2025-03-10 17:37:48 +08:00
zhouwenxuan
7f77c99ba0 prevent dropdown menu to be obscured by popover 2025-03-10 14:46:01 +08:00
Ranjiwei
5aefc8373c
Merge pull request #7550 from haiwen/add-thumbnail-server
add thumbnail server
2025-03-10 14:43:15 +08:00
lian
699da59aff
rm get ldap user num (#7531) 2025-03-10 14:29:56 +08:00
Aries
4fb7552622
Optimize/tag formatter (#7578)
* optimize tag ui

* optimize

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-10 14:28:23 +08:00
Michael An
55719ccbdf
remove react old defaultProps (#7576) 2025-03-10 14:27:08 +08:00
Aries
00707d1d05
Optimize/table view toolbar (#7567)
* optimize table view toolbar

* optimize

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-10 14:17:09 +08:00
孙永强
0cafb5b3f8 optimize share link get thumbnail 2025-03-10 13:28:57 +08:00
杨顺强
7b293d0771 update sdoc editor version 2025-03-10 11:56:08 +08:00
Aries
5b1e3af9cb
fix dropdown menu ui of views_more_operation (#7577)
* fix dropdown menu ui of views_more_operation

* fix metadata-tree-view state error

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-10 11:48:03 +08:00
llj
b857a42c9a
[library content view] fixed the position of the operation menu for folder/file items displayed in the bottom of the page (#7565) 2025-03-10 10:21:14 +08:00
zhouwenxuan
9f36050ed9 fix dropdown menu z-index 2025-03-09 21:43:50 +08:00
zhouwenxuan
587f709568 fix column type dropdown menu 2025-03-09 21:34:37 +08:00
awu0403
2a78a8e8c3
add md test (#7494)
update test view

update load md

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-03-09 16:29:57 +08:00
Michael An
56901e2ac1
change readonly path UI (#7573) 2025-03-07 16:48:36 +08:00
awu0403
98e01baa8e
add metrics api (#7539)
* add metrics api

update

* update

* update

* Update __init__.py

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-03-07 16:42:23 +08:00
Michael An
ebeafd74ab
fix bug in external view image (#7572) 2025-03-07 16:07:11 +08:00
Ranjiwei
884b4b1ed2
initiate (#7523) 2025-03-07 13:40:24 +08:00
Aries
b2f2dc4d0b
fix record context menu doesn't show on row sequence (#7568)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-06 18:03:23 +08:00
lian
3697c777f1
update error msg when transfer repo to dept failed (#7564) 2025-03-05 22:17:05 +08:00
Michael An
a83adb7929
use image last modified time to remove browser cache (#7554)
* use image last modified time to remove browser cache

* remove useless
2025-03-05 17:01:54 +08:00
Michael An
e8de9ba848
remove useless divider in more menu (#7559) 2025-03-05 13:46:47 +08:00
Michael An
a8785ec879
change sdoc submenu direction (#7557)
* change sdoc submenu direction

* fix warnings
2025-03-05 12:43:04 +08:00
Michael An
4a4d28222b
fix search repo press enter (#7555) 2025-03-05 10:03:33 +08:00
Aries
e551580e67
optimize map interaction in side panel (#7553)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-04 20:23:02 +08:00
Aries
5014855e79
fix props of image dialog in map view (#7552)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-04 20:22:27 +08:00
Aries
9c32fe6833
fix layout of people view (#7551)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-04 20:21:31 +08:00
Aries
801a4a86cf
optimize arrow key down event on elements within tag editor (#7549)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-04 12:48:13 +08:00
Aries
1230dda8bd
optimize add view menu list (#7548)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-04 12:47:40 +08:00
Aries
cc0d3a281c
optimize add column interaction (#7546)
* fix icon props error

* optimize add column interaction

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-04 12:47:13 +08:00
孙永强
fe331d95d2 add thumbnail server
preview heic

check jwt

fix share dir show image

update internal check

add-share-link-access-check

update
2025-03-03 17:21:16 +08:00
Michael An
6cb0522a4c Merge branch 'master' into 13.0 2025-03-03 17:15:35 +08:00
Michael An
a5dbce0f8b
01 no shared Wiki tip (#7540) 2025-03-03 17:05:39 +08:00
JoinTyang
c9b82c70cd
read ai config from env (#7542) 2025-03-03 15:39:15 +08:00
Michael An
74c2b61b39
hide v1 wikis header when no v1 wikis (#7538) 2025-03-03 10:16:53 +08:00
JoinTyang
98ca860a16
Merge pull request #7537 from haiwen/support_use_dev_model
Support use dev model
2025-03-03 10:09:25 +08:00
杨顺强
c98fd74f66 fix plain markdown editor error 2025-03-02 17:43:42 +08:00
Aries
f942992b81
update grid images layout (#7536)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-01 17:31:54 +08:00
杨顺强
dc60da99c5 update sdoc version 2025-03-01 17:21:56 +08:00
杨顺强
27dae8ce74 update sdoc version 2025-03-01 16:10:30 +08:00
杨顺强
c59e7aeb2d update sdoc version 2025-03-01 15:43:54 +08:00
Michael An
0fadf04686 fix import package bug 2025-03-01 15:20:51 +08:00
zheng.shen
b9acd92b8a support use dev model 2025-03-01 15:20:22 +08:00
Michael An
bbe090bdb6 fix package diff 2025-03-01 15:08:45 +08:00
Aries
afd5d42abc
refactor insert column popover (#7535)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-03-01 12:29:48 +08:00
Jerry Ren
890880a5c8
refactor(metadata): remove ui-component (#7492)
1. ModalPortal
2. Icon
3. IconBtn
4. Loading
5. CenteredLoading
6. ClickOutside
7. SearchInput
8. Switch
9. CustomizeAddTool
10. SfCalendar
11. SfFilterCalendar
12. CustomizeSelect
13. CustomizePopover
14. FieldDisplaySettings
15. Formatters
16. remove duplicate codes
2025-03-01 10:12:48 +08:00
JoinTyang
67083238c2
optimize people cover cache (#7534) 2025-03-01 10:05:51 +08:00
杨国璇
21f96dd369
Merge pull request #7533 from haiwen/fix-toggle-metadata-file
fix: toggle metadata file
2025-02-28 17:58:00 +08:00
杨国璇
36dd38ffa0 fix: toggle metadata file 2025-02-28 17:52:37 +08:00
Michael An
7cda15e19c Merge branch 'master' into 13.0 2025-02-28 15:14:06 +08:00
Michael An
e7f43e16f2
fix detail.resolve_comment is vacant (#7530) 2025-02-27 18:25:47 +08:00
awu0403
3899577631
optimize notification (#7521)
* update

* update

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-02-27 17:00:33 +08:00
Aries
2fb65580e4
update local storage in detail (#7529)
Co-authored-by: -g <aries@Mac.local>
2025-02-27 16:16:21 +08:00
Michael An
bbd4f15cca
13.0-fix click user notification tab (#7528) 2025-02-27 15:13:58 +08:00
Michael An
32a1639aca Merge branch 'master' into 13.0 2025-02-27 13:25:57 +08:00
杨顺强
b499dd87cc update sdoc-editor version 2025-02-27 11:44:38 +08:00
Aries
c9e2dea463
fix tag files selection area (#7527)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-27 11:33:19 +08:00
lian
a7fb61a4d4
Org active (#7496)
* admin set org active/inactive

* update

* update

* update
2025-02-27 11:12:48 +08:00
Michael An
ea3a7798b9
change icons (#7526) 2025-02-27 10:34:58 +08:00
Aries
314512d353
Feature/add portrait to groups (#7497)
* fix close button error

* update people selection ui

* add people to groups

* optimize people selector ui

* batch update

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-26 21:23:42 +08:00
Aries
295f694baa
show metadata in image previewer (#7525)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-26 16:15:17 +08:00
Aries
d4b2e75b7e
fix selected item match error in recently used list (#7524)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-26 16:08:22 +08:00
Ranjiwei
33c4c76608
Merge pull request #7470 from haiwen/link-invite-to-join-group-13.0
invite user
2025-02-26 13:49:16 +08:00
Michael An
e44962be9a change button style 2025-02-26 11:42:54 +08:00
Michael An
47f016e6e9 change dialog style 2025-02-26 10:10:19 +08:00
杨顺强
88bb921c91 merge master to 13.0 2025-02-26 10:07:17 +08:00
Aries
9bcbab1761
support multiple selection by shift key in gallery (#7518)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-25 17:53:04 +08:00
Michael An
96fd59b54e
change search button style (#7519) 2025-02-25 17:15:29 +08:00
Jerry Ren
453deac354
Merge pull request #7520 from haiwen/fix-warnings
fix warnings
2025-02-25 17:00:33 +08:00
renjie-run
966fc65a9d fix warnings 2025-02-25 16:56:16 +08:00
Aries
30d0a34afa
Feature/tags view file operations (#7464)
* add basic file operations

* add tag view toolbar operations

* add share and rename

* optimize

* optimize

* update codes

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
Co-authored-by: renjie-run <rj.aiyayao@gmail.com>
2025-02-25 16:48:59 +08:00
Ranjiwei
49896f53ef
Merge pull request #7082 from haiwen/update-user-notifications
Update user notifications
2025-02-25 16:21:07 +08:00
Aries
d1ac836de6
update new tag icon (#7516)
* update new tag icon

* fix font style

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-25 12:05:51 +08:00
Michael An
617a8fab72 Merge branch 'master' into 13.0 2025-02-25 12:01:46 +08:00
Aries
669c423b07
add feedback (#7514)
* add feedback

* optimize

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-25 11:39:29 +08:00
r350178982
67dd7e5eed Update models.py 2025-02-25 10:44:11 +08:00
r350178982
a8a2fd6382 update 2025-02-25 10:42:47 +08:00
Aries
d66695954d
update view type change tips (#7513)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-25 09:52:32 +08:00
Aries
13912eea7e
update merge tags selector ui (#7512)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-24 18:26:19 +08:00
llj
b461005c18
['manage group members' dialog] keep this dialog open after clicking the 'cancel' button in the 'select group members' dialog (#7511) 2025-02-24 17:51:43 +08:00
Aries
0a11ce8282
fix context menu position (#7510)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-24 17:05:48 +08:00
llj
b4ed45225b
[library content view] move 'share' into the dropdown menu for each folder/file item (#7508) 2025-02-24 16:54:19 +08:00
Aries
2f4b1e76e5
optimize ui (#7509)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-24 16:04:06 +08:00
Michael An
cd5f4511ab update react-image-lightbox version 2025-02-24 15:13:03 +08:00
Michael An
78cb08d5a1
external link fix abuse report style (#7507)
* external link fix abuse report style

* change codes
2025-02-24 14:03:09 +08:00
Aries
5bca4562fb
fix ui (#7506)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-24 14:02:26 +08:00
Michael An
7d421b9afd
fix create group repo style (#7505) 2025-02-24 12:11:52 +08:00
Michael An
89c37f7656
fix context menu position error in react 18 (#7504) 2025-02-24 12:11:05 +08:00
Aries
ebc97ad6ac
fix selectedRange state error (#7499)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-24 11:34:48 +08:00
awu0403
35f30ab1cd
update metadata map (#7482)
* update metadata map

optimize

* optimize

* update v2

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
2025-02-24 11:33:04 +08:00
杨顺强
bdc9aa24ec
optimize code (#7503) 2025-02-24 10:55:17 +08:00
孙永强
9807cbd752 remove sdoc notification count cache 2025-02-24 10:12:16 +08:00
Michael An
b5a52668e9 change notification dialog style 2025-02-24 10:12:16 +08:00
Michael An
3f17045e47 change UI 1 2025-02-24 10:12:16 +08:00
孙永强
4f87a9e477 optimize ui 2025-02-24 10:12:16 +08:00
r350178982
1fb671ad31 Update notifications.py 2025-02-24 10:12:16 +08:00
r350178982
3bde4555d0 update 2025-02-24 10:12:16 +08:00
r350178982
7d1e9e11ea Update notifications.py 2025-02-24 10:12:16 +08:00
孙永强
ef77d90280 add update all notification API 2025-02-24 10:12:16 +08:00
孙永强
f4558a0f46 optimize 2025-02-24 10:12:16 +08:00
孙永强
b1c44da366 optimize 2025-02-24 10:12:16 +08:00
孙永强
631320c889 complete 2025-02-24 10:12:16 +08:00
孙永强
0f77313761 update 2025-02-24 10:12:16 +08:00
孙永强
27dcce694b Update notifications.py 2025-02-24 10:12:13 +08:00
孙永强
f6f606cb8f optimize 2025-02-24 10:01:45 +08:00
孙永强
f00b81f8d8 invite user 2025-02-24 10:01:41 +08:00
Michael An
175803baba
change table column icon color (#7501) 2025-02-22 16:00:30 +08:00
Aries
a39765072c
fix tag category selection state (#7500)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-21 21:20:18 +08:00
llj
f4be8814c5
[repo trash] redesigned it for mobile (#7502) 2025-02-21 21:17:49 +08:00
杨顺强
e1eb01b439 update sdoc version and sdoc translate 2025-02-21 18:26:13 +08:00
awu0403
d91cdad79e
Update transfer repo add audit log (#7480)
* add admin log

* update

* update

* update

* add org transfer log

* optimize

* Update file-transfer-log.js

* add db_index

* add-operator

* update

* file --> repo

* Update mysql.sql

* update

* update

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
2025-02-21 18:12:25 +08:00
Aries
83d615d3ed
Feature/view conversion (#7483)
* support view conversion

* optimize

* fix view properties migration

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-21 16:42:18 +08:00
llj
30ecd7a385
[wiki] added a resizing bar (enable users to resize the width of side… (#7489)
* [wiki] added a resizing bar (enable users to resize the width of side panel and main panel)

* [wiki] main panel: fixed conditions for 'DocInfo'

* [wiki] resize: make it independent of the other pages
2025-02-21 12:07:39 +08:00
Michael An
6d8a61627e Merge branch 'master' into 13.0 2025-02-20 17:59:59 +08:00
Aries
7a94d66511
add image deletion button on table view image previewer (#7475)
* add image deletion button on table view image previewer

* optimize

* check permission before delete image

* optimize

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-20 16:25:51 +08:00
Aries
27abc38e7e
update drag handler (#7490)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-20 12:09:22 +08:00
杨顺强
5a76c6b2ae update sdoc version and sdoc translate 2025-02-19 16:48:35 +08:00
Michael An
71966dd729 Merge branch 'master' into 13.0 2025-02-19 15:02:15 +08:00
Aries
659ada62d3
fix selection state error after copy items (#7474)
* fix selection state error after copy items

* optimize

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-19 14:55:01 +08:00
Aries
d9019be22f
fix currentMode state modified by accident (#7471)
* fix currentMode state modified by accident

* change parameter text

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
Co-authored-by: Michael An <2331806369@qq.com>
2025-02-19 14:48:47 +08:00
Aries
9692c5e398
optimize thumbnail size in gallery (#7486)
Co-authored-by: zhouwenxuan <aries@Mac.local>
2025-02-19 13:46:24 +08:00
Aries
c957dd238f
Feature/modify tag links by drag and drop (#7469)
* change tags link by drag and drop

* optimize

* optimize drag image

* optimize drag effect

* update codes

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
Co-authored-by: renjie-run <rj.aiyayao@gmail.com>
2025-02-19 10:24:30 +08:00
Jerry Ren
680006a883
refactor(react): replace findDOMNode by ref (#7479) 2025-02-18 16:56:37 +08:00
Michael An
e242c77202
fix 13.0 org-admin reach-router bug (#7481) 2025-02-18 14:04:39 +08:00
杨顺强
02c2b98299 fix tldraw style 2025-02-18 10:43:29 +08:00
Jerry Ren
d8c6c0c684
Merge pull request #7477 from haiwen/fix-edit-tag-error
fix(tags): commit error
2025-02-18 10:32:40 +08:00
Michael An
2b74a02a67
13.0 fix reactstrap Label style (#7478)
* 01 change dialog Label

* 02 change form-label style
2025-02-18 10:31:05 +08:00
Michael An
b386240f28
fix paginator style (#7476) 2025-02-17 20:39:27 +08:00
renjie-run
9dce302e1e fix(tags): commit error 2025-02-17 18:25:26 +08:00
杨顺强
2e0fdf1049 fix change left forder node bug 2025-02-17 17:36:34 +08:00
杨顺强
cdd059fdb9 fix tldraw editor bug 2025-02-17 17:04:14 +08:00
Michael An
1cb4e37207
clean seahub_react.css cache (#7473) 2025-02-17 17:02:37 +08:00
Michael An
ef8cd06f72
fix reactstrap 9.x DropdownMenu style (#7472) 2025-02-17 17:02:02 +08:00
llj
79413cb4fd
Shared dir view redesign (#7468)
* [shared dir view] redesigned it

- changed to a full-screen 'side panel & main panel' layout
- added a 'folder tree' panel, a current path bar with an operation dropdown
  menu, a toolbar for selected items, a 'view mode' menu, and a 'sort'
  menu

* [shared dir view] added a resizing bar (enable users to resize the width of side panel and main panel)

* [shared dir view] path bar: added a 'plus' icon to the dropdown menu toggle when 'upload' is offered in the menu

* [shared dir view] folder tree: cleaned up the code

* [shared dir view] improved 'visit a folder'

* [shared dir view] improvements & cleanup
2025-02-17 14:58:30 +08:00
杨国璇
06410d217d
feat: react warning (#7467)
Co-authored-by: 杨国璇 <ygx@Hello-word.local>
2025-02-17 11:50:29 +08:00
杨顺强
91b9a7840f update sdoc version and sdoc translate 2025-02-17 11:15:07 +08:00
杨顺强
21162095fb merge master to 13.0 2025-02-17 10:16:35 +08:00
skywalker
df2d8def00 dist-13.0 2025-02-17 10:03:16 +08:00
杨国璇
23c400a437
fix: scheduleUpdate (#7466)
Co-authored-by: 杨国璇 <ygx@Hello-word.local>
2025-02-15 16:11:08 +08:00
杨国璇
236ecf987a
feat: metadata ui 1.0 (#7465)
Co-authored-by: 杨国璇 <ygx@Hello-word.local>
2025-02-15 15:50:15 +08:00
杨顺强
e2e2e98c43 update sdoc-editor version 2025-02-14 14:14:05 +08:00
杨顺强
ccab6f1552
Update react version 3 (#7453)
* update react version

* update reactstrap

* optimize code

* update react-select version

* update react-responsive

* update react-chartjs version

* update qrocde version

* update seafile-editor version

* update tldraw editor version

* fix code bug
2025-02-14 14:04:25 +08:00
977 changed files with 34392 additions and 16782 deletions

View File

@ -4,12 +4,15 @@ on:
push:
branches:
- master
- "12.0"
- "13.0"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest

View File

@ -6,10 +6,20 @@ on:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
REDIS_HOST: localhost
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
services:
redis:
image: redis:latest
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 3
ports:
- 6379:6379
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
@ -25,6 +35,7 @@ jobs:
sudo apt-get install -y libfuse-dev cmake re2c flex sqlite3
sudo apt-get install -y libssl-dev libsasl2-dev libldap2-dev libonig-dev
sudo apt-get install -y libxml2 libxml2-dev libjwt-dev
sudo apt-get install -y libhiredis-dev
- name: clone and build
run: |
@ -39,9 +50,14 @@ jobs:
pip install -r test-requirements.txt
sudo rm -rf /usr/lib/python3/dist-packages/pytz/
- name: Set REDIS_HOST environment variable
run: |
echo "REDIS_HOST=localhost" >> $GITHUB_ENV
- name: run pytest
run: |
cd $GITHUB_WORKSPACE
rm -r tests/seahub/repo_metadata
export CCNET_CONF_DIR=/tmp/ccnet SEAFILE_CONF_DIR=/tmp/seafile-data TRAVIS=1 SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub
if ./tests/test_seahub_changes.sh; then ./tests/seahubtests.sh init && ./tests/seahubtests.sh runserver && ./tests/seahubtests.sh test; else true; fi

View File

@ -1,4 +1,4 @@
'use strict';
const path = require('path');
const fs = require('fs');
@ -22,8 +22,10 @@ const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
// );
// reset by custom
const HOST = process.env.HOST || '0.0.0.0';
const PORT = process.env.PORT || '3000';
const CONFIG_HOST = process.env.HOST;
const isRunInDocker = CONFIG_HOST === '0.0.0.0';
const HOST = isRunInDocker ? '127.0.0.1' : CONFIG_HOST;
const PORT = process.env.PORT || '3001';
const publicPath = process.env.PUBLIC_PATH || '/assets/bundles/';
const publicUrlOrPath = `http://${HOST}:${PORT}${publicPath}`;
@ -79,5 +81,4 @@ module.exports = {
};
module.exports.moduleFileExtensions = moduleFileExtensions;

View File

@ -438,12 +438,17 @@ module.exports = function (webpackEnv) {
ref: true,
},
},
{
loader: require.resolve('file-loader'),
{ loader: 'svgo-loader',
options: {
name: 'static/media/[name].[hash].[ext]',
},
},
plugins: [
'removeTitle',
'removeStyleElement',
'cleanupIDs',
'inlineStyles',
'removeXMLProcInst',
]
}
}
],
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
@ -596,7 +601,16 @@ module.exports = function (webpackEnv) {
test: /\.svg$/,
use: [
{
loader: 'svg-sprite-loader', options: {}
loader: require.resolve('@svgr/webpack'),
options: {
prettier: false,
svgo: false,
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
titleProp: true,
ref: true,
},
},
{ loader: 'svgo-loader', options: {
plugins: [
@ -627,6 +641,12 @@ module.exports = function (webpackEnv) {
// Make sure to add the new loader(s) before the "file" loader.
],
},
{
test: /\.m?js$/,
resolve: {
fullySpecified: false
}
}
].filter(Boolean),
},
plugins: [

View File

@ -2,6 +2,7 @@ const paths = require('./paths');
const entryFiles = {
tldrawEditor: '/tldrawEditor.js',
excalidrawEditor: '/excalidraw-editor.js',
markdownEditor: '/index.js',
plainMarkdownEditor: '/pages/plain-markdown-editor/index.js',
TCAccept: '/tc-accept.js',
@ -24,6 +25,7 @@ const entryFiles = {
sharedFileViewAudio: '/shared-file-view-audio.js',
sharedFileViewDocument: '/shared-file-view-document.js',
sharedFileViewSpreadsheet: '/shared-file-view-spreadsheet.js',
sharedFileViewExdraw: '/shared-file-view-exdraw.js',
sharedFileViewSdoc: '/shared-file-view-sdoc.js',
sharedFileViewUnknown: '/shared-file-view-unknown.js',
historyTrashFileView: '/history-trash-file-view.js',

File diff suppressed because it is too large Load Diff

View File

@ -8,19 +8,19 @@
"@codemirror/view": "^6.34.1",
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@gatsbyjs/reach-router": "1.3.9",
"@seafile/react-image-lightbox": "3.0.7",
"@excalidraw/excalidraw": "^0.18.0",
"@gatsbyjs/reach-router": "2.0.1",
"@seafile/react-image-lightbox": "4.0.2",
"@seafile/resumablejs": "1.1.16",
"@seafile/sdoc-editor": "1.0.223",
"@seafile/sdoc-editor": "2.0.54",
"@seafile/seafile-calendar": "0.0.28",
"@seafile/seafile-editor": "1.0.133",
"@seafile/sf-metadata-ui-component": "^0.0.68",
"@seafile/stldraw-editor": "0.1.7",
"@seafile/seafile-editor": "2.0.2",
"@seafile/stldraw-editor": "1.0.1",
"@uiw/codemirror-extensions-langs": "^4.19.4",
"@uiw/codemirror-themes": "^4.23.5",
"@uiw/react-codemirror": "^4.19.4",
"axios": "^1.8.2",
"chart.js": "3.6.0",
"chart.js": "4.4.7",
"classnames": "^2.2.6",
"codemirror": "^6.0.1",
"copy-to-clipboard": "^3.0.8",
@ -33,23 +33,24 @@
"i18next-xhr-backend": "^3.1.2",
"is-hotkey": "0.2.0",
"MD5": "^1.3.0",
"mdast-util-gfm-autolink-literal": "2.0.0",
"object-assign": "4.1.1",
"prop-types": "^15.8.1",
"qrcode.react": "^1.0.1",
"react": "17.0.2",
"qrcode.react": "4.2.0",
"react": "18.3.1",
"react-app-polyfill": "^2.0.0",
"react-chartjs-2": "4.0.0",
"react-chartjs-2": "5.3.0",
"react-cookies": "^0.1.0",
"react-dnd": "^2.6.0",
"react-dnd-html5-backend": "^2.6.0",
"react-dom": "17.0.2",
"react-dom": "18.3.1",
"react-i18next": "^10.12.2",
"react-responsive": "9.0.2",
"react-select": "5.7.0",
"react-mentions": "4.4.10",
"react-responsive": "10.0.0",
"react-select": "5.9.0",
"react-transition-group": "4.4.5",
"reactstrap": "8.9.0",
"socket.io-client": "^2.2.0",
"svg-sprite-loader": "^6.0.11",
"reactstrap": "9.2.3",
"socket.io-client": "^4.8.1",
"svgo-loader": "^3.0.1",
"unified": "^7.0.0",
"url-parse": "^1.4.3",
@ -63,7 +64,7 @@
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js --env=jsdom",
"dev": "export NODE_ENV=development && node config/server.js"
"dev": "export NODE_ENV=development && node --max-old-space-size=4096 config/server.js"
},
"browserslist": {
"production": [

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import ReactDom from 'react-dom';
import { Router, navigate } from '@gatsbyjs/reach-router';
import { createRoot } from 'react-dom/client';
import { Router, navigate, LocationProvider, globalHistory } from '@gatsbyjs/reach-router';
import MediaQuery from 'react-responsive';
import { Modal } from 'reactstrap';
import { siteRoot, siteTitle, mediaUrl, faviconPath } from './utils/constants';
@ -8,6 +8,7 @@ import { Utils, isMobile } from './utils/utils';
import SystemNotification from './components/system-notification';
import EventBus from './components/common/event-bus';
import Header from './components/header';
import SystemUserNotification from './components/system-user-notification';
import SidePanel from './components/side-panel';
import ResizeBar from './components/resize-bar';
import {
@ -157,19 +158,6 @@ class App extends Component {
}
};
onGroupChanged = (groupID) => {
setTimeout(function () {
let url;
if (groupID) {
url = siteRoot + 'group/' + groupID + '/';
}
else {
url = siteRoot + 'libraries/';
}
window.location = url.toString();
}, 1);
};
tabItemClick = (tabName, groupID) => {
let pathPrefix = [];
if (groupID || this.dirViewPanels.indexOf(tabName) > -1) {
@ -291,6 +279,7 @@ class App extends Component {
return (
<React.Fragment>
<SystemNotification />
<SystemUserNotification />
<Header
isSidePanelClosed={isSidePanelClosed}
onCloseSidePanel={this.onCloseSidePanel}
@ -343,7 +332,7 @@ class App extends Component {
<InvitationsView path={siteRoot + 'invitations/'} />
<FilesActivities path={siteRoot + 'dashboard'} />
<MyFileActivities path={siteRoot + 'my-activities'} />
<GroupView path={siteRoot + 'group/:groupID'} onGroupChanged={this.onGroupChanged} />
<GroupView path={siteRoot + 'group/:groupID'} />
<LinkedDevices path={siteRoot + 'linked-devices'} />
<ShareAdminLibraries path={siteRoot + 'share-admin-libs'} />
<ShareAdminFolders path={siteRoot + 'share-admin-folders'} />
@ -374,4 +363,9 @@ class App extends Component {
}
}
ReactDom.render(<App />, document.getElementById('wrapper'));
const root = createRoot(document.getElementById('wrapper'));
root.render(
<LocationProvider history={globalHistory}>
<App />
</LocationProvider>
);

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:#999999;}
</style>
<title>filter-circled</title>
<g id="filter-circled">
<path id="形状结合" class="st0" d="M16,1c8.3,0,15,6.7,15,15s-6.7,15-15,15S1,24.3,1,16S7.7,1,16,1z M16,4C9.4,4,4,9.4,4,16
s5.4,12,12,12s12-5.4,12-12S22.6,4,16,4z M20,20c0.6,0,1,0.4,1,1s-0.4,1-1,1h-8c-0.6,0-1-0.4-1-1s0.4-1,1-1H20z M22,15
c0.6,0,1,0.4,1,1s-0.4,1-1,1H10c-0.6,0-1-0.4-1-1s0.4-1,1-1H22z M24,10c0.6,0,1,0.4,1,1s-0.4,1-1,1H8c-0.6,0-1-0.4-1-1s0.4-1,1-1
H24z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 846 B

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" xmlns="http://www.w3.org/2000/svg" aria-labelledby="title" viewBox="0 0 32 32" preserveAspectRatio="xMidYMid meet" fill="currentColor" width="48" height="48" title="view-back"><title id="title">view-back</title><g><path d="M19.768,23.89c0.354,-0.424 0.296,-1.055 -0.128,-1.408c-1.645,-1.377 -5.465,-4.762 -6.774,-6.482c1.331,-1.749 5.1,-5.085 6.774,-6.482c0.424,-0.353 0.482,-0.984 0.128,-1.408c-0.353,-0.425 -0.984,-0.482 -1.409,-0.128c-1.839,1.532 -5.799,4.993 -7.2,6.964c-0.219,0.312 -0.409,0.664 -0.409,1.054c0,0.39 0.19,0.742 0.409,1.053c1.373,1.932 5.399,5.462 7.2,6.964l0.001,0.001c0.424,0.354 1.055,0.296 1.408,-0.128Z"></path></g></svg>

After

Width:  |  Height:  |  Size: 780 B

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" xmlns="http://www.w3.org/2000/svg" aria-labelledby="title" viewBox="0 0 32 32" preserveAspectRatio="xMidYMid meet" fill="currentColor" width="48" height="48" title="view-forward"><title id="title">view-forward</title><g><path d="M12.982,23.89c-0.354,-0.424 -0.296,-1.055 0.128,-1.408c1.645,-1.377 5.465,-4.762 6.774,-6.482c-1.331,-1.749 -5.1,-5.085 -6.774,-6.482c-0.424,-0.353 -0.482,-0.984 -0.128,-1.408c0.353,-0.425 0.984,-0.482 1.409,-0.128c1.839,1.532 5.799,4.993 7.2,6.964c0.219,0.312 0.409,0.664 0.409,1.054c0,0.39 -0.19,0.742 -0.409,1.053c-1.373,1.932 -5.399,5.462 -7.2,6.964l-0.001,0.001c-0.424,0.354 -1.055,0.296 -1.408,-0.128Z"></path></g></svg>

After

Width:  |  Height:  |  Size: 790 B

View File

@ -1 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?><svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M13.0123,19.3233l7.07107,-7.07107l-9.40841,-9.40842l-6.84754,-0.297719c-0.0144722,-0.00062923 -0.028965,-0.00062923 -0.0434372,0c-0.275882,0.0119949 -0.489804,0.245365 -0.477809,0.521247l0.297719,6.84754l9.40842,9.40842Zm-10.7052,-16.2125c-0.0359846,-0.827645 0.605783,-1.52776 1.43343,-1.56374c0.0434167,-0.00188768 0.086895,-0.00188768 0.130312,0l7.23616,0.314616l10.3906,10.3906l-8.48528,8.48528l-10.3906,-10.3906l-0.314616,-7.23616Zm5.75544,4.1917c-0.585786,0.585786 -1.53553,0.585786 -2.12132,0c-0.585786,-0.585786 -0.585786,-1.53553 0,-2.12132c0.585786,-0.585786 1.53553,-0.585786 2.12132,0c0.585786,0.585786 0.585786,1.53553 0,2.12132Zm-0.707107,-0.707107c0.195262,-0.195262 0.195262,-0.511845 0,-0.707107c-0.195262,-0.195262 -0.511845,-0.195262 -0.707107,0c-0.195262,0.195262 -0.195262,0.511845 0,0.707107c0.195262,0.195262 0.511845,0.195262 0.707107,0Z" stroke="none"></path></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="15px" height="15px" viewBox="0 0 15 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M13.7515136,5.93572645 L8.28976642,0.621080351 C7.879571,0.222593643 7.32333684,-0.000847143079 6.7436592,2.41376939e-06 L2.18654926,2.41376939e-06 C0.979680972,2.41376939e-06 0,0.950120356 0,2.12526321 L0,6.55780208 C0,7.12087054 0.230271291,7.66193631 0.64044202,8.06098482 L6.10527317,13.3776312 C6.95834688,14.2074603 8.34132992,14.2074603 9.19440364,13.3776312 L13.7515136,8.94309205 C14.6047509,8.11299113 14.6047509,6.76682748 13.7515136,5.93672657 L13.7515136,5.93572645 Z M12.1129482,8.06825704 L8.34330379,11.7695888 C7.91766113,12.1834794 7.22778724,12.1834794 6.80214458,11.7695888 L2.11187052,7.05109716 C1.90701239,6.85210202 1.79175754,6.58211021 1.79144107,6.30047085 L1.79144107,2.74606218 C1.79169751,2.46489086 1.9068179,2.19534855 2.11144974,1.99679547 C2.31608157,1.79824239 2.59344239,1.68696248 2.88245014,1.68746078 L6.64338195,1.68746078 C6.93283334,1.68746078 7.20969987,1.79859659 7.41396155,1.99731949 L12.1119801,6.56794622 C12.5379286,6.98234468 12.5379286,7.65385856 12.1119801,8.06825704 L12.1129482,8.06825704 Z" id="image2"></path>
<path d="M5.19151264,2.9941275 C4.08690365,2.9941275 3.19144113,3.86530858 3.19144113,4.93996491 C3.19144113,6.01462124 4.08690365,6.88580232 5.19151264,6.88580232 C6.29612164,6.88580232 7.19158415,6.01462124 7.19158415,4.93996491 C7.19158415,3.86530858 6.29612164,2.9941275 5.19151264,2.9941275 L5.19151264,2.9941275 Z M4.88150156,5.43615345 C4.69675086,5.322975 4.58976949,5.12109806 4.60216374,4.9090349 C4.61455799,4.69697174 4.74437157,4.50819414 4.94111847,4.41611989 C5.13786537,4.32404563 5.37049132,4.34320761 5.54852541,4.4661535 C5.81754925,4.64750572 5.88755949,5.00514843 5.70588358,5.2700074 C5.52420766,5.53486636 5.15787671,5.60921928 4.88250159,5.43712636 L4.88150156,5.43615345 Z" id="image"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import QRCode from 'qrcode.react';
import { QRCodeSVG } from 'qrcode.react';
import { Button, Popover, PopoverBody } from 'reactstrap';
import { gettext } from '../utils/constants';
@ -16,8 +16,7 @@ class ButtonQR extends React.Component {
this.state = {
isPopoverOpen: false
};
this.btnID = 'btn-' + Math.random().toString().substr(2, 5);
this.btn = null;
}
togglePopover = () => {
@ -30,14 +29,16 @@ class ButtonQR extends React.Component {
const { link } = this.props;
const { isPopoverOpen } = this.state;
return (
<div className="ml-2">
<Button outline color="primary" className="btn-icon btn-qr-code-icon sf3-font sf3-font-qr-code" id={this.btnID} onClick={this.togglePopover} type="button"></Button>
<Popover placement="bottom" isOpen={isPopoverOpen} target={this.btnID} toggle={this.togglePopover}>
<PopoverBody>
<QRCode value={link} size={128} />
<p className="m-0 mt-1 text-center" style={{ 'maxWidth': '128px' }}>{gettext('Scan the QR code to view the shared content directly')}</p>
</PopoverBody>
</Popover>
<div className="ml-2" ref={ref => this.btn = ref}>
<Button outline color="primary" className="btn-icon btn-qr-code-icon sf3-font sf3-font-qr-code" onClick={this.togglePopover} type="button"></Button>
{this.btn && (
<Popover placement="bottom" isOpen={isPopoverOpen} target={this.btn} toggle={this.togglePopover}>
<PopoverBody>
<QRCodeSVG value={link} size={128} />
<p className="m-0 mt-1 text-center" style={{ 'maxWidth': '128px' }}>{gettext('Scan the QR code to view the shared content directly')}</p>
</PopoverBody>
</Popover>
)}
</div>
);
}

View File

@ -0,0 +1,7 @@
.sf-centered-loading {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -0,0 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Loading from '../loading';
import './index.css';
function CenteredLoading(props) {
return (
<div className={classnames('sf-centered-loading', props.className)}>
<Loading />
</div>
);
}
CenteredLoading.propTypes = {
className: PropTypes.string,
};
export default CenteredLoading;

View File

@ -21,7 +21,7 @@
.lds-ripple div {
position: absolute;
border: 4px solid #eb8205;
border: 4px solid #EC8000;
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;

View File

@ -1,26 +1,30 @@
.add-item-btn {
cursor: pointer;
display: flex;
align-items: center;
height: 40px;
font-size: 14px;
font-weight: 500;
border-top: 1px solid #dedede;
background: #fff;
padding: 0 1rem;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
position: relative;
height: 30px;
padding: 0 10px;
overflow: hidden;
}
.add-item-btn:hover {
background-color: #f5f5f5;
cursor: pointer;
background: #f5f5f5;
}
.add-item-btn .add-new-option {
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.add-item-btn .seafile-multicolor-icon-add-table {
margin-right: 10px;
font-size: 12px;
font-weight: 600;
transform: none;
fill: #666;
}
.add-item-btn .description {
flex: 1;
}

View File

@ -1,13 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import '../../css/common-add-tool.css';
import Icon from '../icon';
function CommonAddTool(props) {
const { callBack, footerName, className, addIconClassName } = props;
import './index.css';
function CommonAddTool({ callBack, footerName, className, addIconClassName, hideIcon, style }) {
return (
<div className={`add-item-btn ${className ? className : ''}`} onClick={(e) => {callBack(e);}}>
<span className={`sf3-font sf3-font-enlarge mr-2 ${addIconClassName || ''}`}></span>
<span className='add-new-option' title={footerName}>{footerName}</span>
<div className={`add-item-btn ${className ? className : ''}`} style={style} onClick={(e) => {callBack(e);}}>
{!hideIcon && <Icon symbol="add-table" className={addIconClassName} />}
<span className="description text-truncate">{footerName}</span>
</div>
);
}

View File

@ -1,6 +1,5 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import { siteRoot, isPro, gettext, appAvatarURL, enableSSOToThirdpartWebsite } from '../../utils/constants';
@ -35,10 +34,6 @@ class Account extends Component {
this.handleProps();
}
getContainer = () => {
return ReactDOM.findDOMNode(this);
};
handleProps = () => {
if (this.state.showInfo) {
this.addEvents();
@ -61,9 +56,7 @@ class Account extends Component {
handleDocumentClick = (e) => {
if (e && (e.which === 3 || (e.type === 'keyup' && e.which !== Utils.keyCodes.tab))) return;
const container = this.getContainer();
if (container.contains(e.target) && container !== e.target && (e.type !== 'keyup' || e.which === Utils.keyCodes.tab)) {
if (this.accountDOM && this.accountDOM.contains(e.target) && this.accountDOM !== e.target && (e.type !== 'keyup' || e.which === Utils.keyCodes.tab)) {
return;
}
@ -101,8 +94,9 @@ class Account extends Component {
renderMenu = () => {
let data;
const { isStaff, isOrgStaff, isInstAdmin } = this.state;
const { isAdminPanel = false } = this.props;
if (this.props.isAdminPanel) {
if (isAdminPanel) {
if (isStaff) {
data = {
url: siteRoot,
@ -148,7 +142,7 @@ class Account extends Component {
render() {
return (
<div id="account">
<div id="account" ref={ref => this.accountDOM = ref}>
<a id="my-info" href="#" onClick={this.onClickAccount} className="account-toggle no-deco d-none d-md-block" aria-label={gettext('View profile and more')}>
{this.renderAvatar()}
</a>
@ -180,10 +174,6 @@ class Account extends Component {
}
}
Account.defaultProps = {
isAdminPanel: false
};
Account.propTypes = propTypes;
export default Account;

View File

@ -10,4 +10,14 @@ export const EVENT_BUS_TYPE = {
RESTORE_IMAGE: 'restore_image',
OPEN_MARKDOWN: 'open_markdown',
// migrate tags
OPEN_TREE_PANEL: 'open_tree_panel',
OPEN_LIBRARY_SETTINGS_TAGS: 'open_library_settings_tags',
// tags
TAG_STATUS: 'tag_status',
TAGS_DATA: 'tags_data',
SELECT_TAG: 'select_tag',
UPDATE_SELECTED_TAG: 'update_selected_tag',
};

View File

@ -1,10 +1,9 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import SearchInput from '../search-input';
import SearchInput from '../../search-input';
import Option from './option';
import KeyCodes from '../../../constants/keyCodes';
import ClickOutside from './click-outside';
import './select-option-group.css';
@ -26,6 +25,7 @@ class SelectOptionGroup extends Component {
componentDidMount() {
window.addEventListener('keydown', this.onHotKey);
document.addEventListener('mousedown', this.handleDocumentClick);
setTimeout(() => {
this.resetMenuStyle();
}, 1);
@ -35,8 +35,13 @@ class SelectOptionGroup extends Component {
this.filterOptions = null;
this.timer && clearTimeout(this.timer);
window.removeEventListener('keydown', this.onHotKey);
document.removeEventListener('mousedown', this.handleDocumentClick);
}
handleDocumentClick = (e) => {
this.props.onClickOutside(e);
};
resetMenuStyle = () => {
const { isInModal, position } = this.props;
const { top, height } = this.optionGroupRef.getBoundingClientRect();
@ -170,27 +175,25 @@ class SelectOptionGroup extends Component {
};
}
return (
<ClickOutside onClickOutside={this.props.onClickOutside}>
<div
className={classnames('pt-0 option-group', className ? 'option-group-' + className : '')}
ref={(ref) => this.optionGroupRef = ref}
style={style}
onMouseDown={this.onMouseDown}
>
<div className="option-group-search position-relative">
<SearchInput
className="option-search-control"
autoFocus={isInModal}
placeholder={searchPlaceholder}
onChange={this.onChangeSearch}
ref={this.searchInputRef}
/>
</div>
<div className="option-group-content" ref={(ref) => this.optionGroupContentRef = ref}>
{this.renderOptGroup(searchVal)}
</div>
<div
className={classnames('pt-0 option-group', className ? 'option-group-' + className : '')}
ref={(ref) => this.optionGroupRef = ref}
style={style}
onMouseDown={this.onMouseDown}
>
<div className="option-group-search position-relative">
<SearchInput
className="option-search-control"
autoFocus={isInModal}
placeholder={searchPlaceholder}
onChange={this.onChangeSearch}
ref={this.searchInputRef}
/>
</div>
</ClickOutside>
<div className="option-group-content" ref={(ref) => this.optionGroupContentRef = ref}>
{this.renderOptGroup(searchVal)}
</div>
</div>
);
}
}

View File

@ -4,6 +4,8 @@ import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { gettext, siteRoot } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import { processor } from '@seafile/seafile-editor';
import '../../css/notice-item.css';
const propTypes = {
noticeItem: PropTypes.object.isRequired,
@ -17,6 +19,7 @@ const MSG_TYPE_REPO_SHARE_TO_GROUP = 'repo_share_to_group';
const MSG_TYPE_REPO_TRANSFER = 'repo_transfer';
const MSG_TYPE_FILE_UPLOADED = 'file_uploaded';
const MSG_TYPE_FOLDER_UPLOADED = 'folder_uploaded';
const MSG_TYPE_FILE_COMMENT = 'file_comment';
// const MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted';
const MSG_TYPE_REPO_MONITOR = 'repo_monitor';
const MSG_TYPE_DELETED_FILES = 'deleted_files';
@ -24,6 +27,8 @@ const MSG_TYPE_SAML_SSO_FAILED = 'saml_sso_failed';
const MSG_TYPE_REPO_SHARE_PERM_CHANGE = 'repo_share_perm_change';
const MSG_TYPE_REPO_SHARE_PERM_DELETE = 'repo_share_perm_delete';
const MSG_TYPE_FACE_CLUSTER = 'face_cluster';
const MSG_TYPE_SEADOC_REPLY = 'reply';
const MSG_TYPE_SEADOC_COMMENT = 'comment';
dayjs.extend(relativeTime);
@ -34,35 +39,44 @@ class NoticeItem extends React.Component {
let noticeType = noticeItem.type;
let detail = noticeItem.detail;
if (noticeType === MSG_TYPE_FILE_COMMENT) {
let avatar_url = detail.author_avatar_url;
let author = detail.author_name;
let fileName = detail.file_name;
let fileUrl = siteRoot + 'lib/' + detail.repo_id + '/' + 'file' + detail.file_path;
// 1. handle translate
let notice = gettext('File {file_link} has a new comment form user {author}.');
// 2. handle xss(cross-site scripting)
notice = notice.replace('{file_link}', `{tagA}${fileName}{/tagA}`);
notice = notice.replace('{author}', author);
notice = Utils.HTMLescape(notice);
// 3. add jump link
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(fileUrl)}>`);
notice = notice.replace('{/tagA}', '</a>');
return { avatar_url, notice };
}
if (noticeType === MSG_TYPE_ADD_USER_TO_GROUP) {
let avatar_url = detail.group_staff_avatar_url;
let groupStaff = detail.group_staff_name;
// group name does not support special characters
let userHref = siteRoot + 'profile/' + encodeURIComponent(detail.group_staff_email) + '/';
let groupHref = siteRoot + 'group/' + detail.group_id + '/';
let groupName = detail.group_name;
let username = detail.group_staff_name;
let notice = gettext('User {user_link} has added you to {group_link}');
let userLink = '<a href=' + userHref + '>' + Utils.HTMLescape(groupStaff) + '</a>';
let groupLink = '<a href=' + groupHref + '>' + Utils.HTMLescape(groupName) + '</a>';
notice = notice.replace('{user_link}', userLink);
notice = notice.replace('{group_link}', groupLink);
return { avatar_url, notice };
return { avatar_url, notice, username };
}
if (noticeType === MSG_TYPE_REPO_SHARE) {
let avatar_url = detail.share_from_user_avatar_url;
let shareFrom = detail.share_from_user_name;
let repoName = detail.repo_name;
let repoUrl = siteRoot + 'library/' + detail.repo_id + '/' + repoName + '/';
let path = detail.path;
let notice = '';
// 1. handle translate
@ -71,21 +85,17 @@ class NoticeItem extends React.Component {
} else { // share folder
notice = gettext('{share_from} has shared a folder named {repo_link} to you.');
}
// 2. handle xss(cross-site scripting)
notice = notice.replace('{share_from}', shareFrom);
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
notice = Utils.HTMLescape(notice);
// 3. add jump link
notice = notice.replace('{tagA}', `<a href='${Utils.encodePath(repoUrl)}'>`);
notice = notice.replace('{/tagA}', '</a>');
return { avatar_url, notice };
return { avatar_url, notice, username: shareFrom };
}
if (noticeType === MSG_TYPE_REPO_SHARE_PERM_CHANGE) {
let avatar_url = detail.share_from_user_avatar_url;
let shareFrom = detail.share_from_user_name;
let permission = detail.permission;
@ -99,22 +109,18 @@ class NoticeItem extends React.Component {
} else { // share folder
notice = gettext('{share_from} has changed the permission of folder {repo_link} to {permission}.');
}
// 2. handle xss(cross-site scripting)
notice = notice.replace('{share_from}', shareFrom);
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
notice = notice.replace('{permission}', permission);
notice = Utils.HTMLescape(notice);
// 3. add jump link
notice = notice.replace('{tagA}', `<a href='${Utils.encodePath(repoUrl)}'>`);
notice = notice.replace('{/tagA}', '</a>');
return { avatar_url, notice };
return { avatar_url, notice, username: shareFrom };
}
if (noticeType === MSG_TYPE_REPO_SHARE_PERM_DELETE) {
let avatar_url = detail.share_from_user_avatar_url;
let shareFrom = detail.share_from_user_name;
let repoName = detail.repo_name;
@ -126,26 +132,20 @@ class NoticeItem extends React.Component {
} else { // share folder
notice = gettext('{share_from} has cancelled the sharing of folder {repo_name}.');
}
// 2. handle xss(cross-site scripting)
notice = notice.replace('{share_from}', shareFrom);
notice = notice.replace('{repo_name}', repoName);
notice = Utils.HTMLescape(notice);
return { avatar_url, notice };
return { avatar_url, notice, username: shareFrom };
}
if (noticeType === MSG_TYPE_REPO_SHARE_TO_GROUP) {
let avatar_url = detail.share_from_user_avatar_url;
let shareFrom = detail.share_from_user_name;
let repoName = detail.repo_name;
let repoUrl = siteRoot + 'library/' + detail.repo_id + '/' + repoName + '/';
let groupUrl = siteRoot + 'group/' + detail.group_id + '/';
let groupName = detail.group_name;
let path = detail.path;
let notice = '';
// 1. handle translate
@ -154,60 +154,50 @@ class NoticeItem extends React.Component {
} else {
notice = gettext('{share_from} has shared a folder named {repo_link} to group {group_link}.');
}
// 2. handle xss(cross-site scripting)
notice = notice.replace('{share_from}', shareFrom);
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
notice = notice.replace('{group_link}', `{tagB}${groupName}{/tagB}`);
notice = Utils.HTMLescape(notice);
// 3. add jump link
notice = notice.replace('{tagA}', `<a href='${Utils.encodePath(repoUrl)}'>`);
notice = notice.replace('{/tagA}', '</a>');
notice = notice.replace('{tagB}', `<a href='${Utils.encodePath(groupUrl)}'>`);
notice = notice.replace('{/tagB}', '</a>');
return { avatar_url, notice };
return { avatar_url, notice, username: shareFrom };
}
if (noticeType === MSG_TYPE_REPO_TRANSFER) {
let avatar_url = detail.transfer_from_user_avatar_url;
let repoOwner = detail.transfer_from_user_name;
let repoName = detail.repo_name;
let repoUrl = siteRoot + 'library/' + detail.repo_id + '/' + repoName + '/';
// 1. handle translate
let notice = gettext('{user} has transfered a library named {repo_link} to you.');
// 2. handle xss(cross-site scripting)
notice = notice.replace('{user}', repoOwner);
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
notice = Utils.HTMLescape(notice);
// 3. add jump link
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(repoUrl)}>`);
notice = notice.replace('{/tagA}', '</a>');
return { avatar_url, notice };
return { avatar_url, notice, username: repoOwner };
}
if (noticeType === MSG_TYPE_FILE_UPLOADED) {
let avatar_url = detail.uploaded_user_avatar_url;
let fileName = detail.file_name;
let fileLink = siteRoot + 'lib/' + detail.repo_id + '/' + 'file' + detail.file_path;
let folderName = detail.folder_name;
let folderLink = siteRoot + 'library/' + detail.repo_id + '/' + detail.repo_name + detail.folder_path;
let notice = '';
if (detail.repo_id) { // todo is repo exist ?
// 1. handle translate
notice = gettext('A file named {upload_file_link} is uploaded to {uploaded_link}.');
// 2. handle xss(cross-site scripting)
notice = notice.replace('{upload_file_link}', `{tagA}${fileName}{/tagA}`);
notice = notice.replace('{uploaded_link}', `{tagB}${folderName}{/tagB}`);
notice = Utils.HTMLescape(notice);
// 3. add jump link
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(fileLink)}>`);
notice = notice.replace('{/tagA}', '</a>');
@ -216,7 +206,6 @@ class NoticeItem extends React.Component {
} else {
// 1. handle translate
notice = gettext('A file named {upload_file_link} is uploaded.');
// 2. handle xss(cross-site scripting)
notice = notice.replace('{upload_file_link}', `${fileName}`);
notice = Utils.HTMLescape(notice);
@ -340,17 +329,11 @@ class NoticeItem extends React.Component {
}
if (noticeType === MSG_TYPE_DELETED_FILES) {
const {
repo_id,
repo_name,
} = detail;
const { repo_id, repo_name } = detail;
const repoURL = `${siteRoot}library/${repo_id}/${encodeURIComponent(repo_name)}/`;
const repoLink = `<a href=${repoURL} target="_blank">${Utils.HTMLescape(repo_name)}</a>`;
let notice = gettext('Your library {libraryName} has recently deleted a large number of files.');
notice = notice.replace('{libraryName}', repoLink);
return { avatar_url: null, notice };
}
@ -370,15 +353,70 @@ class NoticeItem extends React.Component {
if (noticeType === MSG_TYPE_SAML_SSO_FAILED) {
const { error_msg } = detail;
let notice = gettext(error_msg);
return { avatar_url: null, notice };
}
if (noticeType === MSG_TYPE_SEADOC_COMMENT) {
let avatar_url = detail.avatar_url;
let notice = detail.comment;
let username = detail.user_name;
let is_resolved = detail.is_resolved;
let sdoc_name = detail.sdoc_name;
const repo_id = detail.repo_id;
const sdoc_path = detail.sdoc_path;
const sdoc_href = siteRoot + 'lib/' + repo_id + '/file' + sdoc_path;
let sdoc_link = '<a href=' + sdoc_href + '>' + sdoc_name + '</a>';
processor.process(notice, (error, vfile) => {
notice = String(vfile);
});
if (is_resolved) {
if (detail.resolve_comment && detail.resolve_comment !== '\u200B') {
notice = gettext('Marked "{resolve_comment}" as resolved in document {sdoc_link}');
notice = notice.replace('{resolve_comment}', detail.resolve_comment);
notice = notice.replace('{sdoc_link}', sdoc_link);
} else {
notice = gettext('Marked as resolved in document {sdoc_link}');
notice = notice.replace('{sdoc_link}', sdoc_link);
}
} else {
notice = gettext('Added a new comment in document {sdoc_link}:').replace('{sdoc_link}', sdoc_link) + notice;
}
return { avatar_url, username, notice };
}
if (noticeType === MSG_TYPE_SEADOC_REPLY) {
let avatar_url = detail.avatar_url;
let notice = detail.reply;
let username = detail.user_name;
let is_resolved = detail.is_resolved;
let sdoc_name = detail.sdoc_name;
const repo_id = detail.repo_id;
const sdoc_path = detail.sdoc_path;
const sdoc_href = siteRoot + 'lib/' + repo_id + '/file' + sdoc_path;
let sdoc_link = '<a href=' + sdoc_href + '>' + sdoc_name + '</a>';
processor.process(notice, (error, vfile) => {
notice = String(vfile);
});
if (is_resolved) {
if (detail.resolve_comment && detail.resolve_comment !== '\u200B') {
notice = gettext('Marked "{resolve_comment}" as resolved in document {sdoc_link}');
notice = notice.replace('{resolve_comment}', detail.resolve_comment);
notice = notice.replace('{sdoc_link}', sdoc_link);
} else {
notice = gettext('Marked as resolved in document {sdoc_link}');
notice = notice.replace('{sdoc_link}', sdoc_link);
}
} else {
notice = gettext('Added a new reply in document {sdoc_link}:').replace('{sdoc_link}', sdoc_link) + notice;
}
return { avatar_url, username, notice };
}
// if (noticeType === MSG_TYPE_GUEST_INVITATION_ACCEPTED) {
// }
return { avatar_url: null, notice: null };
return { avatar_url: null, notice: null, username: null };
}
onNoticeItemClick = () => {
@ -391,16 +429,19 @@ class NoticeItem extends React.Component {
render() {
let noticeItem = this.props.noticeItem;
let { avatar_url, notice } = this.generatorNoticeInfo();
let { avatar_url, username, notice } = this.generatorNoticeInfo();
if (!avatar_url && !notice) {
return '';
}
return this.props.tr ? (
<tr className={noticeItem.seen ? 'read' : 'unread font-weight-bold'}>
<tr className='notification-item'>
<td className="text-center">
{!noticeItem.seen && <span className="notification-point" onClick={this.onMarkNotificationRead}></span>}
</td>
<td>
<img src={avatar_url} width="32" height="32" className="avatar" alt="" />
<span className="ml-2 notification-user-name">{username || gettext('System')}</span>
</td>
<td className="pr-1 pr-md-8">
<p className="m-0" dangerouslySetInnerHTML={{ __html: notice }}></p>
@ -410,13 +451,21 @@ class NoticeItem extends React.Component {
</td>
</tr>
) : (
<li onClick={this.onNoticeItemClick} className={noticeItem.seen ? 'read' : 'unread'}>
<div className="notice-item">
<div className="main-info">
<img src={avatar_url} width="32" height="32" className="avatar" alt=""/>
<p className="brief" dangerouslySetInnerHTML={{ __html: notice }}></p>
<li className='notification-item' onClick={this.onNoticeItemClick}>
<div className="notification-item-header">
{!noticeItem.seen &&
<span className="notification-point" onClick={this.onMarkNotificationRead}></span>
}
<div className="notification-header-info">
<div className="notification-user-detail">
<img className="notification-user-avatar" src={avatar_url} alt="" />
<span className="ml-2 notification-user-name">{username || gettext('System')}</span>
</div>
<span className="notification-time">{dayjs(noticeItem.time).fromNow()}</span>
</div>
<p className="time">{dayjs(noticeItem.time).fromNow()}</p>
</div>
<div className="notification-content-wrapper">
<div dangerouslySetInnerHTML={{ __html: notice }}></div>
</div>
</li>
);

View File

@ -5,7 +5,7 @@
.notification-container {
position: absolute;
background: #fff;
width: 320px;
width: 400px;
right: -16px;
top: -1px;
border-radius: 3px;
@ -27,22 +27,12 @@
font-size: 16px;
font-weight: 600;
position: relative;
overflow: hidden;
}
.notification-container .notification-header .notification-close-icon {
.notification-container .notification-header .seahub-modal-btn {
position: absolute;
right: 14px;
height: 24px;
width: 24px;
text-align: center;
cursor: pointer;
color: #000;
opacity: 0.5;
font-weight: 700;
}
.notification-container .notification-header .notification-close-icon:hover {
opacity: 0.75;
}
.notification-container .notification-body {
@ -65,21 +55,15 @@
margin-left: 20px;
}
.notification-container .notification-body .mark-notifications {
color: #b4b4b4;
.notification-container .mark-all-read {
color: #666;
cursor: pointer;
border-bottom: 1px solid #ededed;
height: 36px;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 1rem;
}
.notification-container .notification-body .mark-notifications:hover {
text-decoration: underline;
}
.notification-body .notification-list-container {
max-height: 260px;
overflow: auto;
@ -190,3 +174,33 @@
.notification-body .notification-footer:hover {
text-decoration: underline;
}
.notification-container .notification-body .mark-notifications {
display: flex;
justify-content: space-between;
border-bottom: 1px solid #ededed;
padding-left: 15px;
}
.notification-container .notification-body .mark-notifications .mark-all-read:hover {
text-decoration: underline;
}
.notification-container .notification-body .nav .nav-item .nav-link {
height: 46px;
margin-right: 15px;
margin-left: 15px;
font-size: 14px;
color: #212529;
}
.notification-container .notification-body .nav .nav-item .nav-link.active {
color: #ED7109;
}
@media (max-width: 768px) {
.notification-container {
right: -60px;
width: 360px;
}
}

View File

@ -1,26 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Popover } from 'reactstrap';
import { gettext } from '../../../utils/constants';
import SeahubModalCloseIcon from '../seahub-modal-close';
import './index.css';
export default class NotificationPopover extends React.Component {
static propTypes = {
headerText: PropTypes.string.isRequired,
bodyText: PropTypes.string.isRequired,
footerText: PropTypes.string.isRequired,
onNotificationListToggle: PropTypes.func,
onNotificationDialogToggle: PropTypes.func,
listNotifications: PropTypes.func,
onMarkAllNotifications: PropTypes.func,
children: PropTypes.any,
};
static defaultProps = {
headerText: '',
bodyText: '',
footerText: '',
};
class NotificationPopover extends React.Component {
componentDidMount() {
document.addEventListener('mousedown', this.handleOutsideClick, true);
@ -47,8 +33,12 @@ export default class NotificationPopover extends React.Component {
}
};
tabItemClick = (tab) => {
this.props.tabItemClick(tab);
};
render() {
const { headerText, bodyText, footerText } = this.props;
const { headerText = '', bodyText = '', footerText = '', currentTab, generalNoticeListUnseen, discussionNoticeListUnseen } = this.props;
return (
<Popover
className="notification-wrapper"
@ -59,17 +49,44 @@ export default class NotificationPopover extends React.Component {
placement="bottom"
>
<div className="notification-container" ref={ref => this.notificationContainerRef = ref}>
<div className="notification-header">
<div className="notification-header modal">
{headerText}
<span className="sf3-font sf3-font-x-01 notification-close-icon" onClick={this.props.onNotificationListToggle}></span>
<SeahubModalCloseIcon toggle={this.props.onNotificationListToggle} />
</div>
<div className="notification-body">
<div className="mark-notifications" onClick={this.props.onMarkAllNotifications}>{bodyText}</div>
<div className="mark-notifications">
<ul className="nav">
<li className="nav-item" onClick={() => this.tabItemClick('general')}>
<span className={`nav-link ${currentTab === 'general' ? 'active' : ''}`}>
{gettext('General')}
{generalNoticeListUnseen > 0 && <span>({generalNoticeListUnseen})</span>}
</span>
</li>
<li className="nav-item" onClick={() => this.tabItemClick('discussion')}>
<span className={`nav-link ${currentTab === 'discussion' ? 'active' : ''}`}>
{gettext('Discussion')}
{discussionNoticeListUnseen > 0 && <span>({discussionNoticeListUnseen})</span>}
</span>
</li>
</ul>
<span className="mark-all-read" onClick={this.props.onMarkAllNotifications}>
{bodyText}
</span>
</div>
{currentTab === 'general' &&
<div className="notification-list-container" onScroll={this.onHandleScroll} ref={ref => this.notificationListRef = ref}>
<div ref={ref => this.notificationsWrapperRef = ref}>
{this.props.children}
</div>
</div>
}
{currentTab === 'discussion' &&
<div className="notification-list-container" onScroll={this.onHandleScroll} ref={ref => this.notificationListRef = ref}>
<div ref={ref => this.notificationsWrapperRef = ref}>
{this.props.children}
</div>
</div>
}
<div className="notification-footer" onClick={this.onNotificationDialogToggle}>{footerText}</div>
</div>
</div>
@ -77,3 +94,20 @@ export default class NotificationPopover extends React.Component {
);
}
}
NotificationPopover.propTypes = {
headerText: PropTypes.string.isRequired,
bodyText: PropTypes.string.isRequired,
footerText: PropTypes.string.isRequired,
onNotificationListToggle: PropTypes.func,
onNotificationDialogToggle: PropTypes.func,
listNotifications: PropTypes.func,
onMarkAllNotifications: PropTypes.func,
tabItemClick: PropTypes.func,
children: PropTypes.any,
currentTab: PropTypes.string,
generalNoticeListUnseen: PropTypes.number,
discussionNoticeListUnseen: PropTypes.number,
};
export default NotificationPopover;

View File

@ -12,15 +12,21 @@ class Notification extends React.Component {
super(props);
this.state = {
showNotice: false,
unseenCount: 0,
noticeList: [],
totalUnseenCount: 0,
generalNoticeList: [],
discussionNoticeList: [],
currentTab: 'general',
isShowNotificationDialog: this.getInitDialogState(),
};
}
componentDidMount() {
seafileAPI.getUnseenNotificationCount().then(res => {
this.setState({ unseenCount: res.data.unseen_count });
seafileAPI.listAllNotifications().then(res => {
this.setState({
totalUnseenCount: res.data.total_unseen_count,
generalNoticeListUnseen: res.data.general.unseen_count,
discussionNoticeListUnseen: res.data.discussion.unseen_count
});
});
}
@ -30,7 +36,7 @@ class Notification extends React.Component {
seafileAPI.updateNotifications();
this.setState({
showNotice: false,
unseenCount: 0
totalUnseenCount: 0
});
} else {
this.loadNotices();
@ -38,28 +44,65 @@ class Notification extends React.Component {
}
};
tabItemClick = (tab) => {
const { currentTab } = this.state;
if (currentTab === tab) return;
this.setState({
showNotice: true,
currentTab: tab
});
};
loadNotices = () => {
let page = 1;
let perPage = 5;
seafileAPI.listNotifications(page, perPage).then(res => {
let noticeList = res.data.notification_list;
this.setState({ noticeList: noticeList });
let perPage = 25;
seafileAPI.listAllNotifications(page, perPage).then(res => {
let generalNoticeList = res.data.general.notification_list;
let discussionNoticeList = res.data.discussion.notification_list;
let generalNoticeListUnseen = res.data.general.unseen_count;
let discussionNoticeListUnseen = res.data.discussion.unseen_count;
this.setState({
generalNoticeList: generalNoticeList,
discussionNoticeList: discussionNoticeList,
generalNoticeListUnseen: generalNoticeListUnseen,
discussionNoticeListUnseen: discussionNoticeListUnseen
});
});
};
onNoticeItemClick = (noticeItem) => {
let noticeList = this.state.noticeList.map(item => {
if (item.id === noticeItem.id) {
item.seen = true;
}
return item;
});
seafileAPI.markNoticeAsRead(noticeItem.id);
let unseenCount = this.state.unseenCount === 0 ? 0 : this.state.unseenCount - 1;
this.setState({
noticeList: noticeList,
unseenCount: unseenCount,
});
if (this.state.currentTab === 'general') {
let noticeList = this.state.generalNoticeList.map(item => {
if (item.id === noticeItem.id) {
item.seen = true;
}
return item;
});
let totalUnseenCount = this.state.totalUnseenCount === 0 ? 0 : this.state.totalUnseenCount - 1;
let generalNoticeListUnseen = this.state.generalNoticeListUnseen === 0 ? 0 : this.state.generalNoticeListUnseen - 1;
this.setState({
generalNoticeList: noticeList,
totalUnseenCount: totalUnseenCount,
generalNoticeListUnseen: generalNoticeListUnseen
});
seafileAPI.markNoticeAsRead(noticeItem.id);
}
if (this.state.currentTab === 'discussion') {
let noticeList = this.state.discussionNoticeList.map(item => {
if (item.id === noticeItem.id) {
item.seen = true;
}
return item;
});
let totalUnseenCount = this.state.totalUnseenCount === 0 ? 0 : this.state.totalUnseenCount - 1;
let discussionNoticeListUnseen = this.state.discussionNoticeListUnseen === 0 ? 0 : this.state.discussionNoticeListUnseen - 1;
this.setState({
discussionNoticeList: noticeList,
totalUnseenCount: totalUnseenCount,
discussionNoticeListUnseen: discussionNoticeListUnseen
});
seafileAPI.markSdocNoticeAsRead(noticeItem.id);
}
};
@ -79,43 +122,103 @@ class Notification extends React.Component {
};
onMarkAllNotifications = () => {
seafileAPI.updateNotifications().then(() => {
this.setState({
unseenCount: 0,
let generalNoticeListUnseen = this.state.generalNoticeListUnseen;
let discussionNoticeListUnseen = this.state.discussionNoticeListUnseen;
if (this.state.currentTab === 'general') {
seafileAPI.updateNotifications().then((res) => {
this.setState({
generalNoticeList: this.state.generalNoticeList.map(item => {
item.seen = true;
return item;
}),
generalNoticeListUnseen: 0,
totalUnseenCount: discussionNoticeListUnseen
});
}).catch((error) => {
this.setState({
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}).catch((error) => {
this.setState({
errorMsg: Utils.getErrorMsg(error, true)
} else if (this.state.currentTab === 'discussion') {
seafileAPI.updateSdocNotifications().then((res) => {
this.setState({
discussionNoticeList: this.state.discussionNoticeList.map(item => {
item.seen = true;
return item;
}),
discussionNoticeListUnseen: 0,
totalUnseenCount: generalNoticeListUnseen
});
}).catch((error) => {
this.setState({
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
});
}
};
updateTotalUnseenCount = (noticeType) => {
if (noticeType === 'general') {
this.setState({
generalNoticeListUnseen: 0,
totalUnseenCount: this.state.discussionNoticeListUnseen
});
} else if (noticeType === 'discussion') {
this.setState({
discussionNoticeListUnseen: 0,
totalUnseenCount: this.state.generalNoticeListUnseen
});
}
};
render() {
const { unseenCount } = this.state;
const { totalUnseenCount, currentTab, generalNoticeList, discussionNoticeList, generalNoticeListUnseen, discussionNoticeListUnseen } = this.state;
return (
<div id="notifications">
<a href="#" onClick={this.onClick} className="no-deco" id="notice-icon" title={gettext('Notifications')} aria-label={gettext('Notifications')}>
<span className="sf2-icon-bell" id="notification-popover"></span>
<span className={`num ${unseenCount ? '' : 'hide'}`}>{unseenCount}</span>
<span className={`num ${totalUnseenCount ? '' : 'hide'}`}>{totalUnseenCount}</span>
</a>
{this.state.showNotice &&
<NotificationPopover
headerText={gettext('Notification')}
bodyText={gettext('Mark all as read')}
footerText={gettext('View all notifications')}
currentTab={currentTab}
onNotificationListToggle={this.onNotificationListToggle}
onNotificationDialogToggle={this.onNotificationDialogToggle}
onMarkAllNotifications={this.onMarkAllNotifications}
tabItemClick={this.tabItemClick}
generalNoticeListUnseen={generalNoticeListUnseen}
discussionNoticeListUnseen={discussionNoticeListUnseen}
>
<ul className="notice-list list-unstyled" id="notice-popover">
{this.state.noticeList.map(item => {
return (<NoticeItem key={item.id} noticeItem={item} onNoticeItemClick={this.onNoticeItemClick}/>);
})}
</ul>
{currentTab === 'general' &&
<ul className="notice-list list-unstyled" id="notice-popover">
{generalNoticeList.map(item => {
return (
<NoticeItem key={item.id} noticeItem={item} onNoticeItemClick={this.onNoticeItemClick}/>
);
})}
</ul>
}
{currentTab === 'discussion' &&
<ul className="notice-list list-unstyled" id="notice-popover">
{discussionNoticeList.map(item => {
return (
<NoticeItem key={item.id} noticeItem={item} onNoticeItemClick={this.onNoticeItemClick}/>
);
})}
</ul>
}
</NotificationPopover>
}
{this.state.isShowNotificationDialog &&
<UserNotificationsDialog onNotificationDialogToggle={this.onNotificationDialogToggle} />
<UserNotificationsDialog
onNotificationDialogToggle={this.onNotificationDialogToggle}
generalNoticeListUnseen={generalNoticeListUnseen}
discussionNoticeListUnseen={discussionNoticeListUnseen}
updateTotalUnseenCount={this.updateTotalUnseenCount}
/>
}
</div>
);

View File

@ -0,0 +1,15 @@
import React from 'react';
import { gettext } from '../../utils/constants';
import '../../css/seahub-modal-header.css';
const SeahubModalCloseIcon = (props) => {
return (
<button type="button" className={`close seahub-modal-btn ${props.className ? props.className : ''}`} data-dismiss="modal" aria-label={gettext('Close')} onClick={props.toggle}>
<span className="seahub-modal-btn-inner">
<i className="sf3-font sf3-font-x-01" aria-hidden="true"></i>
</span>
</button>
);
};
export default SeahubModalCloseIcon;

View File

@ -54,47 +54,17 @@ Option.propTypes = {
}),
};
export default class SeahubSelect extends React.Component {
static propTypes = {
isMulti: PropTypes.bool,
options: PropTypes.array.isRequired,
value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string]),
isSearchable: PropTypes.bool,
isClearable: PropTypes.bool,
placeholder: PropTypes.string,
classNamePrefix: PropTypes.string,
className: PropTypes.string,
form: PropTypes.string,
onChange: PropTypes.func.isRequired,
menuPortalTarget: PropTypes.string,
menuPosition: PropTypes.string,
noOptionsMessage: PropTypes.func,
innerRef: PropTypes.object,
isDisabled: PropTypes.bool,
};
static defaultProps = {
options: [],
value: {},
isDisabled: false,
isSearchable: true,
isClearable: true,
placeholder: '',
isMulti: false,
menuPortalTarget: '.modal',
noOptionsMessage: () => {
return null;
},
};
class SeahubSelect extends React.Component {
getMenuPortalTarget = () => {
return document.querySelector(this.props.menuPortalTarget);
const { menuPortalTarget = '.modal' } = this.props;
return document.querySelector(menuPortalTarget);
};
render() {
const { options, onChange, value, isSearchable, placeholder, isMulti, menuPosition, isClearable, noOptionsMessage,
classNamePrefix, innerRef, isDisabled, form, className } = this.props;
const { options = [], onChange, value = {}, isSearchable = true, placeholder = '',
isMulti = false, menuPosition, isClearable = true, noOptionsMessage = (() => { return null; }),
classNamePrefix, innerRef, isDisabled = false, form, className } = this.props;
return (
<Select
@ -121,3 +91,23 @@ export default class SeahubSelect extends React.Component {
);
}
}
SeahubSelect.propTypes = {
isMulti: PropTypes.bool,
options: PropTypes.array.isRequired,
value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string]),
isSearchable: PropTypes.bool,
isClearable: PropTypes.bool,
placeholder: PropTypes.string,
classNamePrefix: PropTypes.string,
className: PropTypes.string,
form: PropTypes.string,
onChange: PropTypes.func.isRequired,
menuPortalTarget: PropTypes.string,
menuPosition: PropTypes.string,
noOptionsMessage: PropTypes.func,
innerRef: PropTypes.object,
isDisabled: PropTypes.bool,
};
export default SeahubSelect;

View File

@ -228,7 +228,7 @@ class ContextMenu extends React.Component {
onMouseMove={(e) => {e.stopPropagation();}}
>
<DropdownToggle
tag='div'
tag='span'
className="dropdown-item font-weight-normal rounded-0 d-flex align-items-center"
onMouseEnter={this.toggleSubMenuShown.bind(this, menuItem)}
>

View File

@ -2,13 +2,12 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { UncontrolledTooltip } from 'reactstrap';
import { Link } from '@gatsbyjs/reach-router';
import DirOperationToolBar from '../../components/toolbar/dir-operation-toolbar';
import DirOperationToolbar from '../../components/toolbar/dir-operation-toolbar';
import MetadataViewName from '../../metadata/components/metadata-view-name';
import TagViewName from '../../tag/components/tag-view-name';
import { siteRoot, gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import { debounce, Utils } from '../../utils/utils';
import { PRIVATE_FILE_TYPE } from '../../constants';
import { debounce } from '../../metadata/utils/common';
import { EVENT_BUS_TYPE } from '../../metadata/constants';
import { ALL_TAGS_ID } from '../../tag/constants';
@ -34,7 +33,6 @@ const propTypes = {
direntList: PropTypes.array.isRequired,
repoTags: PropTypes.array.isRequired,
filePermission: PropTypes.string,
onFileTagChanged: PropTypes.func.isRequired,
onItemMove: PropTypes.func.isRequired,
loadDirentList: PropTypes.func.isRequired,
};
@ -129,15 +127,19 @@ class DirPath extends React.Component {
return (
<>
<span className="path-split">/</span>
<span className="path-item">{gettext('Views')}</span>
<span className="path-item path-item-read-only">{gettext('Views')}</span>
<span className="path-split">/</span>
<span className="path-item" role={children ? 'button' : null} onClick={children ? this.handleRefresh : () => {}}>
<span
className="path-item path-item-read-only"
role={children ? 'button' : null}
onClick={children ? this.handleRefresh : () => {}}
>
<MetadataViewName id={viewId} />
</span>
{children && (
<>
<span className="path-split">/</span>
<span className="path-item">{children}</span>
<span className="path-item path-item-read-only">{children}</span>
</>
)}
<div className="path-item-refresh" id="sf-metadata-view-refresh" onClick={this.handleRefresh}>
@ -157,13 +159,13 @@ class DirPath extends React.Component {
return (
<>
<span className="path-split">/</span>
<span className="path-item">{gettext('Tags')}</span>
<span className="path-item path-item-read-only">{gettext('Tags')}</span>
<span className="path-split">/</span>
<TagViewName id={tagId} canSelectAllTags={canSelectAllTags} />
{children && (
<>
<span className="path-split">/</span>
<span className="path-item">{children}</span>
<span className="path-item path-item-read-only">{children}</span>
</>
)}
</>
@ -173,8 +175,12 @@ class DirPath extends React.Component {
turnPathToLink = (path) => {
path = path[path.length - 1] === '/' ? path.slice(0, path.length - 1) : path;
const pathList = path.split('/');
if (pathList.includes(PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES)) return this.turnViewPathToLink(pathList);
if (pathList.includes(PRIVATE_FILE_TYPE.TAGS_PROPERTIES)) return this.turnTagPathToLink(pathList);
if (pathList.includes(PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES)) {
return this.turnViewPathToLink(pathList);
}
if (pathList.includes(PRIVATE_FILE_TYPE.TAGS_PROPERTIES)) {
return this.turnTagPathToLink(pathList);
}
let nodePath = '';
let pathElem = pathList.map((item, index) => {
if (item === '') return null;
@ -182,7 +188,7 @@ class DirPath extends React.Component {
return (
<Fragment key={index}>
<span className="path-split">/</span>
<DirOperationToolBar
<DirOperationToolbar
path={this.props.currentPath}
repoID={this.props.repoID}
repoName={this.props.repoName}
@ -199,7 +205,7 @@ class DirPath extends React.Component {
loadDirentList={this.props.loadDirentList}
>
<span className="path-file-name">{item}</span>
</DirOperationToolBar>
</DirOperationToolbar>
</Fragment>
);
} else {
@ -241,19 +247,19 @@ class DirPath extends React.Component {
);
})}
{this.props.pathPrefix && this.props.pathPrefix.length === 0 && (
<Fragment>
<>
<Link to={siteRoot + 'libraries/'} className="path-item normal" onClick={(e) => this.onTabNavClick(e, 'libraries')}>{gettext('Files')}</Link>
<span className="path-split">/</span>
</Fragment>
</>
)}
{!this.props.pathPrefix && (
<Fragment>
<>
<Link to={siteRoot + 'libraries/'} className="path-item normal" onClick={(e) => this.onTabNavClick(e, 'libraries')}>{gettext('Files')}</Link>
<span className="path-split">/</span>
</Fragment>
</>
)}
{(currentPath === '/' || currentPath === '') ?
<DirOperationToolBar
<DirOperationToolbar
path={this.props.currentPath}
repoID={this.props.repoID}
repoName={this.props.repoName}
@ -270,7 +276,7 @@ class DirPath extends React.Component {
loadDirentList={this.props.loadDirentList}
>
<span className="path-repo-name">{repoName}</span>
</DirOperationToolBar> :
</DirOperationToolbar> :
<span className="path-item" data-path="/" onClick={this.onPathClick} role="button">{repoName}</span>
}
{pathElem}

View File

@ -1,23 +1,18 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
import { gettext, enableFileTags } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import TextTranslation from '../../utils/text-translation';
import SeahubPopover from '../common/seahub-popover';
import ListTagPopover from '../popover/list-tag-popover';
import ViewModes from '../../components/view-modes';
import SortMenu from '../../components/sort-menu';
import MetadataViewToolBar from '../../metadata/components/view-toolbar';
import TagsTableSearcher from '../../tag/views/all-tags/tags-table/tags-searcher';
import { PRIVATE_FILE_TYPE } from '../../constants';
import { ALL_TAGS_ID } from '../../tag/constants';
import AllTagsSortSetter from '../../tag/views/all-tags/tags-table/sort-setter';
import TagFilesSortSetter from '../../tag/views/tag-files/sort-setter';
const propTypes = {
repoID: PropTypes.string.isRequired,
userPerm: PropTypes.string,
currentPath: PropTypes.string.isRequired,
updateUsedRepoTags: PropTypes.func.isRequired,
onDeleteRepoTag: PropTypes.func.isRequired,
currentMode: PropTypes.string.isRequired,
switchViewMode: PropTypes.func.isRequired,
isCustomPermission: PropTypes.bool,
@ -31,75 +26,17 @@ const propTypes = {
class DirTool extends React.Component {
constructor(props) {
super(props);
this.state = {
isRepoTagDialogOpen: false,
isDropdownMenuOpen: false,
};
}
toggleDropdownMenu = () => {
this.setState({
isDropdownMenuOpen: !this.state.isDropdownMenuOpen
});
};
hidePopover = (e) => {
if (e) {
let dom = e.target;
while (dom) {
if (typeof dom.className === 'string' && dom.className.includes('tag-color-popover')) return;
dom = dom.parentNode;
}
}
this.setState({ isRepoTagDialogOpen: false });
};
toggleCancel = () => {
this.setState({ isRepoTagDialogOpen: false });
};
getMenu = () => {
const list = [];
const { userPerm, currentPath } = this.props;
if (userPerm !== 'rw' || Utils.isMarkdownFile(currentPath)) {
return list;
}
const { TAGS } = TextTranslation;
if (enableFileTags) {
list.push(TAGS);
}
return list;
};
onMenuItemClick = (item) => {
const { key } = item;
switch (key) {
case 'Tags':
this.setState({ isRepoTagDialogOpen: !this.state.isRepoTagDialogOpen });
break;
}
};
onMenuItemKeyDown = (e, item) => {
if (e.key == 'Enter' || e.key == 'Space') {
this.onMenuItemClick(item);
}
};
onSelectSortOption = (item) => {
const [sortBy, sortOrder] = item.value.split('-');
this.props.sortItems(sortBy, sortOrder);
};
render() {
const menuItems = this.getMenu();
const { isDropdownMenuOpen } = this.state;
const { repoID, currentMode, currentPath, sortBy, sortOrder, viewId, isCustomPermission, onToggleDetail, onCloseDetail } = this.props;
const { currentMode, currentPath, sortBy, sortOrder, viewId, isCustomPermission, onToggleDetail, onCloseDetail } = this.props;
const propertiesText = TextTranslation.PROPERTIES.value;
const isFileExtended = currentPath.startsWith('/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES + '/');
const isTagView = currentPath.startsWith('/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES + '/');
const isAllTagsView = currentPath.split('/').pop() === ALL_TAGS_ID;
if (isFileExtended) {
return (
@ -117,69 +54,22 @@ class DirTool extends React.Component {
if (isTagView) {
return (
<div className="dir-tool">
<TagsTableSearcher />
{isAllTagsView && <TagsTableSearcher />}
{isAllTagsView ? <AllTagsSortSetter /> : <TagFilesSortSetter />}
</div>
);
}
return (
<React.Fragment>
<div className="dir-tool d-flex">
<ViewModes currentViewMode={currentMode} switchViewMode={this.props.switchViewMode} />
<SortMenu sortBy={sortBy} sortOrder={sortOrder} onSelectSortOption={this.onSelectSortOption} />
{(!isCustomPermission) &&
<div className="cur-view-path-btn" onClick={onToggleDetail}>
<span className="sf3-font sf3-font-info" aria-label={propertiesText} title={propertiesText}></span>
</div>
}
{menuItems.length > 0 &&
<Dropdown isOpen={isDropdownMenuOpen} toggle={this.toggleDropdownMenu}>
<DropdownToggle
tag="i"
id="cur-folder-more-op-toggle"
className='cur-view-path-btn sf3-font-more sf3-font'
data-toggle="dropdown"
title={gettext('More operations')}
aria-label={gettext('More operations')}
aria-expanded={isDropdownMenuOpen}
>
</DropdownToggle>
<DropdownMenu right={true}>
{menuItems.map((menuItem, index) => {
if (menuItem === 'Divider') {
return <DropdownItem key={index} divider />;
} else {
return (
<DropdownItem
key={index}
onClick={this.onMenuItemClick.bind(this, menuItem)}
onKeyDown={this.onMenuItemKeyDown.bind(this, menuItem)}
>{menuItem.value}
</DropdownItem>
);
}
})}
</DropdownMenu>
</Dropdown>
}
</div>
{this.state.isRepoTagDialogOpen &&
<SeahubPopover
popoverClassName="list-tag-popover"
target="cur-folder-more-op-toggle"
hideSeahubPopover={this.hidePopover}
hideSeahubPopoverWithEsc={this.hidePopover}
canHideSeahubPopover={true}
boundariesElement={document.body}
placement={'bottom-end'}
>
<ListTagPopover
repoID={repoID}
onListTagCancel={this.toggleCancel}
/>
</SeahubPopover>
<div className="dir-tool d-flex">
<ViewModes currentViewMode={currentMode} switchViewMode={this.props.switchViewMode} />
<SortMenu sortBy={sortBy} sortOrder={sortOrder} onSelectSortOption={this.onSelectSortOption} />
{(!isCustomPermission) &&
<div className="cur-view-path-btn" onClick={onToggleDetail}>
<span className="sf3-font sf3-font-info" aria-label={propertiesText} title={propertiesText}></span>
</div>
}
</React.Fragment>
</div>
);
}

View File

@ -1,7 +1,8 @@
import React from 'react';
import { Popover } from 'reactstrap';
import PropTypes from 'prop-types';
import { KeyCodes } from '../../constants';
import { KeyCodes } from '../constants';
import { getEventClassName } from '../utils/dom';
const propTypes = {
target: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
@ -9,17 +10,17 @@ const propTypes = {
innerClassName: PropTypes.string,
popoverClassName: PropTypes.string,
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
hideSeahubPopover: PropTypes.func.isRequired,
hideSeahubPopoverWithEsc: PropTypes.func,
hidePopover: PropTypes.func.isRequired,
hidePopoverWithEsc: PropTypes.func,
hideArrow: PropTypes.bool,
canHideSeahubPopover: PropTypes.bool,
canHidePopover: PropTypes.bool,
placement: PropTypes.string,
modifiers: PropTypes.object
};
class SeahubPopover extends React.Component {
class CustomizePopover extends React.Component {
SeahubPopoverRef = null;
popoverRef = null;
isSelectOpen = false;
componentDidMount() {
@ -32,28 +33,23 @@ class SeahubPopover extends React.Component {
document.removeEventListener('keydown', this.onKeyDown);
}
getEventClassName = (e) => {
// svg mouseEvent event.target.className is an object
if (!e || !e.target) return '';
return e.target.getAttribute('class') || '';
};
onKeyDown = (e) => {
const { canHideSeahubPopover, hideSeahubPopoverWithEsc } = this.props;
if (e.keyCode === KeyCodes.Escape && typeof hideSeahubPopoverWithEsc === 'function' && !this.isSelectOpen) {
const { canHidePopover = true, hidePopoverWithEsc } = this.props;
if (e.keyCode === KeyCodes.Escape && typeof hidePopoverWithEsc === 'function' && !this.isSelectOpen) {
e.preventDefault();
hideSeahubPopoverWithEsc();
hidePopoverWithEsc();
} else if (e.keyCode === KeyCodes.Enter) {
// Resolve the default behavior of the enter key when entering formulas is blocked
if (canHideSeahubPopover) return;
if (canHidePopover) return;
e.stopImmediatePropagation();
}
};
onMouseDown = (e) => {
if (!this.props.canHideSeahubPopover) return;
if (this.SeahubPopoverRef && e && this.getEventClassName(e).indexOf('popover') === -1 && !this.SeahubPopoverRef.contains(e.target)) {
this.props.hideSeahubPopover(e);
const { canHidePopover = true } = this.props;
if (!canHidePopover) return;
if (this.popoverRef && e && getEventClassName(e).indexOf('popover') === -1 && !this.popoverRef.contains(e.target)) {
this.props.hidePopover(e);
}
};
@ -63,8 +59,8 @@ class SeahubPopover extends React.Component {
render() {
const {
target, boundariesElement, innerClassName, popoverClassName, hideArrow, modifiers,
placement,
target, boundariesElement, innerClassName, popoverClassName, hideArrow = true, modifiers,
placement = 'bottom-start',
} = this.props;
let additionalProps = {};
if (boundariesElement) {
@ -82,7 +78,7 @@ class SeahubPopover extends React.Component {
modifiers={modifiers}
{...additionalProps}
>
<div ref={ref => this.SeahubPopoverRef = ref} onClick={this.onPopoverInsideClick}>
<div ref={ref => this.popoverRef = ref} onClick={this.onPopoverInsideClick}>
{this.props.children}
</div>
</Popover>
@ -90,12 +86,6 @@ class SeahubPopover extends React.Component {
}
}
SeahubPopover.defaultProps = {
placement: 'bottom-start',
hideArrow: true,
canHideSeahubPopover: true
};
CustomizePopover.propTypes = propTypes;
SeahubPopover.propTypes = propTypes;
export default SeahubPopover;
export default CustomizePopover;

View File

@ -0,0 +1,100 @@
.seafile-customize-select {
position: relative;
display: flex;
padding: 0 10px;
border-radius: 3px;
align-items: center;
justify-content: space-between;
max-width: 900px;
user-select: none;
text-align: left;
line-height: 1.5;
background-image: none;
font-size: 14px;
color: #212529;
}
.seafile-customize-select:focus,
.seafile-customize-select.focus {
border-color: #1991eb !important;
box-shadow: 0 0 0 2px rgba(70, 127, 207, 0.25);
}
.seafile-customize-select.disabled:focus,
.seafile-customize-select.focus.disabled,
.seafile-customize-select.disabled:hover {
border-color: rgba(0, 40, 100, 0.12) !important;
box-shadow: unset;
cursor: default;
}
.seafile-customize-select:hover {
cursor: pointer;
border-color: rgb(179, 179, 179);
}
.seafile-customize-select .sf3-font-down {
color: #999;
}
.seafile-customize-select .selected-option {
display: flex;
flex: 1;
overflow: hidden;
flex-wrap: nowrap;
align-items: center;
justify-content: space-between;
background: #fff;
}
.seafile-customize-select .selected-option .custom-select-dropdown-icon {
height: 12px;
width: 12px;
color: #999;
display: flex;
align-items: center;
justify-content: center;
margin-left: 0.5rem;
}
.seafile-customize-select.selector-collaborator .seafile-option-group .seafile-option-group-content,
.seafile-customize-select.selector-group .seafile-option-group .seafile-option-group-content {
padding: 10px;
}
.seafile-customize-select.selector-collaborator .seafile-option-group .seafile-option-group-content {
padding: 10px 0;
}
.seafile-customize-select.selector-collaborator .option {
padding: 5px 0 5px 10px !important;
line-height: 20px;
}
.seafile-customize-select.selector-group .option {
height: 30px;
display: flex;
align-items: center;
}
.seafile-customize-select.selector-group .select-group-option {
justify-content: space-between;
}
.seafile-customize-select.selector-group .selected-option .selected-group {
padding: 0 2px;
background: #eceff4;
border-radius: 3px;
}
.seafile-customize-select .selected-option-show {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.seafile-customize-select .select-placeholder {
line-height: 1;
font-size: 14px;
white-space: nowrap;
}

View File

@ -0,0 +1,173 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import ModalPortal from '../modal-portal';
import SelectOptionGroup from './select-option-group';
import { getEventClassName } from '../../utils/dom';
import './index.css';
class CustomizeSelect extends Component {
constructor(props) {
super(props);
this.state = {
isShowSelectOptions: false
};
}
onSelectToggle = (event) => {
event.preventDefault();
/*
if select is showing, click events do not need to be monitored by other click events,
so it can be closed when other select is clicked.
*/
if (this.state.isShowSelectOptions) event.stopPropagation();
const eventClassName = getEventClassName(event);
if (this.props.readOnly || eventClassName.indexOf('option-search-control') > -1 || eventClassName === 'seafile-option-group-search') return;
// Prevent closing by pressing the space bar in the search input
if (event.target.value === '') return;
this.setState({
isShowSelectOptions: !this.state.isShowSelectOptions
});
};
onClick = (event) => {
if (this.props.isShowSelected && event.target.className.includes('icon-fork-number')) {
return;
}
if (!this.selector.contains(event.target)) {
this.closeSelect();
}
};
closeSelect = () => {
this.setState({ isShowSelectOptions: false });
};
getSelectedOptionTop = () => {
if (!this.selector) return 38;
const { height } = this.selector.getBoundingClientRect();
return height;
};
getFilterOptions = (searchValue) => {
const { options, searchable } = this.props;
if (!searchable) return options || [];
const validSearchVal = searchValue.trim().toLowerCase();
if (!validSearchVal) return options || [];
return options.filter(option => {
const { value, name } = option;
if (typeof name === 'string') {
return name.toLowerCase().indexOf(validSearchVal) > -1;
}
if (typeof value === 'object') {
if (value.column) {
return value.column.name.toLowerCase().indexOf(validSearchVal) > -1;
}
if (value.name) {
return value.name.toLowerCase().indexOf(validSearchVal) > -1;
}
return value.columnOption && value.columnOption.name.toLowerCase().indexOf(validSearchVal) > -1;
}
return false;
});
};
renderDropDownIcon = () => {
const { readOnly, component } = this.props;
if (readOnly) return;
const { DropDownIcon } = component || {};
if (DropDownIcon) {
return (
<div className="custom-select-dropdown-icon">{DropDownIcon}</div>
);
}
return (<i className="sf3-font sf3-font-down" aria-hidden="true"></i>);
};
render() {
const { className, value, options, placeholder, searchable, searchPlaceholder, noOptionsPlaceholder,
readOnly, isInModal, addOptionAble, component } = this.props;
return (
<div
ref={(node) => this.selector = node}
className={classnames('seafile-customize-select custom-select',
{ 'focus': this.state.isShowSelectOptions },
{ 'disabled': readOnly },
className
)}
onClick={this.onSelectToggle}>
<div className="selected-option">
{value && value.label ?
<span className="selected-option-show">{value.label}</span>
:
<span className="select-placeholder">{placeholder}</span>
}
{this.renderDropDownIcon()}
</div>
{this.state.isShowSelectOptions && !isInModal && (
<SelectOptionGroup
value={value}
addOptionAble={addOptionAble}
component={component}
isShowSelected={this.props.isShowSelected}
top={this.getSelectedOptionTop()}
options={options}
onSelectOption={this.props.onSelectOption}
searchable={searchable}
searchPlaceholder={searchPlaceholder}
noOptionsPlaceholder={noOptionsPlaceholder}
onClickOutside={this.onClick}
closeSelect={this.closeSelect}
getFilterOptions={this.getFilterOptions}
supportMultipleSelect={this.props.supportMultipleSelect}
/>
)}
{this.state.isShowSelectOptions && isInModal && (
<ModalPortal>
<SelectOptionGroup
className={className}
value={value}
addOptionAble={addOptionAble}
component={component}
isShowSelected={this.props.isShowSelected}
position={this.selector.getBoundingClientRect()}
isInModal={isInModal}
top={this.getSelectedOptionTop()}
options={options}
onSelectOption={this.props.onSelectOption}
searchable={searchable}
searchPlaceholder={searchPlaceholder}
noOptionsPlaceholder={noOptionsPlaceholder}
onClickOutside={this.onClick}
closeSelect={this.closeSelect}
getFilterOptions={this.getFilterOptions}
supportMultipleSelect={this.props.supportMultipleSelect}
/>
</ModalPortal>
)}
</div>
);
}
}
CustomizeSelect.propTypes = {
className: PropTypes.string,
value: PropTypes.object,
options: PropTypes.array,
placeholder: PropTypes.string,
onSelectOption: PropTypes.func,
readOnly: PropTypes.bool,
searchable: PropTypes.bool,
addOptionAble: PropTypes.bool,
searchPlaceholder: PropTypes.string,
noOptionsPlaceholder: PropTypes.string,
component: PropTypes.object,
supportMultipleSelect: PropTypes.bool,
isShowSelected: PropTypes.bool,
isInModal: PropTypes.bool, // if select component in a modal (option group need ModalPortal to show)
};
export default CustomizeSelect;

View File

@ -0,0 +1,103 @@
.seafile-option-group {
position: absolute;
left: 0;
min-height: 60px;
max-height: 300px;
min-width: 100%;
max-width: 15rem;
padding: 0.5rem 0;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
background: #fff;
border: 1px solid rgba(0, 40, 100, 0.12);
border-radius: 3px;
z-index: 10001;
}
.seafile-option-group .seafile-option-group-search {
width: 100%;
padding: 0 10px 6px 10px;
min-width: 170px;
}
.seafile-option-group-search .form-control {
height: 31px;
}
.seafile-option-group .none-search-result {
height: 100px;
width: 100%;
padding: 10px;
color: #666666;
}
.seafile-option-group .seafile-option-group-content {
max-height: 252px;
overflow-y: auto;
}
.seafile-select-option {
display: block;
width: 100%;
line-height: 24px;
padding: 0.25rem 10px;
clear: both;
font-weight: 400;
text-align: inherit;
background-color: transparent;
border: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.seafile-select-option.seafile-select-option-active {
background-color: #20a0ff;
color: #fff;
cursor: pointer;
}
.seafile-select-option.seafile-select-option-active .select-option-name {
color: #fff;
}
.seafile-select-option:hover .header-icon .seafile-multicolor-icon,
.seafile-select-option.seafile-select-option-active .header-icon .seafile-multicolor-icon {
fill: #fff;
}
.seafile-select-option:not(.seafile-select-option-active):hover .header-icon .seafile-multicolor-icon {
fill: #aaa;
}
.seafile-select-option .select-option-name .single-select-option {
margin: 0 0 0 12px;
}
.seafile-select-option .select-option-name .multiple-select-option {
margin: 0;
}
.seafile-option-group-selector-single-select .select-option-name,
.seafile-option-group-selector-multiple-select .multiple-option-name {
display: flex;
align-items: center;
justify-content: space-between;
}
.seafile-option-group-selector-multiple-select .multiple-check-icon {
display: inline-flex;
width: 20px;
text-align: center;
}
.seafile-option-group-selector-multiple-select .multiple-check-icon .seafile-multicolor-icon-check-mark {
font-size: 12px;
color: #798d99;
}
.seafile-option-group-selector-single-select .seafile-select-option:hover,
.seafile-option-group-selector-single-select .seafile-select-option.seafile-select-option-active,
.seafile-option-group-selector-multiple-select .seafile-select-option:hover,
.seafile-option-group-selector-multiple-select .seafile-select-option.seafile-select-option-active {
background-color: #f5f5f5;
}

View File

@ -0,0 +1,233 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import ClickOutside from '../../click-outside';
import SearchInput from '../../search-input';
import Option from './option';
import { KeyCodes } from '../../../constants';
import './index.css';
const OPTION_HEIGHT = 32;
class SelectOptionGroup extends Component {
constructor(props) {
super(props);
this.state = {
searchVal: '',
activeIndex: -1,
disableHover: false,
};
this.filterOptions = null;
this.timer = null;
}
componentDidMount() {
window.addEventListener('keydown', this.onHotKey);
setTimeout(() => {
this.resetMenuStyle();
}, 1);
}
componentWillUnmount() {
this.filterOptions = null;
this.timer && clearTimeout(this.timer);
window.removeEventListener('keydown', this.onHotKey);
}
resetMenuStyle = () => {
const { isInModal, position } = this.props;
const { top, height } = this.optionGroupRef.getBoundingClientRect();
if (isInModal) {
if (position.y + position.height + height > window.innerHeight) {
this.optionGroupRef.style.top = (position.y - height) + 'px';
}
this.optionGroupRef.style.opacity = 1;
}
else {
if (height + top > window.innerHeight) {
const borderWidth = 2;
this.optionGroupRef.style.top = -1 * (height + borderWidth) + 'px';
}
}
};
onHotKey = (event) => {
const keyCode = event.keyCode;
if (keyCode === KeyCodes.UpArrow) {
this.onPressUp();
} else if (keyCode === KeyCodes.DownArrow) {
this.onPressDown();
} else if (keyCode === KeyCodes.Enter) {
let option = this.filterOptions && this.filterOptions[this.state.activeIndex];
if (option) {
this.props.onSelectOption(option.value);
if (!this.props.supportMultipleSelect) {
this.props.closeSelect();
}
}
} else if (keyCode === KeyCodes.Tab || keyCode === KeyCodes.Escape) {
this.props.closeSelect();
}
};
onPressUp = () => {
if (this.state.activeIndex > 0) {
this.setState({ activeIndex: this.state.activeIndex - 1 }, () => {
this.scrollContent();
});
}
};
onPressDown = () => {
if (this.filterOptions && this.state.activeIndex < this.filterOptions.length - 1) {
this.setState({ activeIndex: this.state.activeIndex + 1 }, () => {
this.scrollContent();
});
}
};
onMouseDown = (e) => {
const { isInModal } = this.props;
// prevent event propagation when click option or search input
if (isInModal) {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
}
};
scrollContent = () => {
const { offsetHeight, scrollTop } = this.optionGroupContentRef;
this.setState({ disableHover: true });
this.timer = setTimeout(() => {
this.setState({ disableHover: false });
}, 500);
if (this.state.activeIndex * OPTION_HEIGHT === 0) {
this.optionGroupContentRef.scrollTop = 0;
return;
}
if (this.state.activeIndex * OPTION_HEIGHT < scrollTop) {
this.optionGroupContentRef.scrollTop = scrollTop - OPTION_HEIGHT;
}
else if (this.state.activeIndex * OPTION_HEIGHT > offsetHeight + scrollTop) {
this.optionGroupContentRef.scrollTop = scrollTop + OPTION_HEIGHT;
}
};
changeIndex = (index) => {
this.setState({ activeIndex: index });
};
onChangeSearch = (searchVal) => {
let value = searchVal || '';
if (value !== this.state.searchVal) {
this.setState({ searchVal: value, activeIndex: -1, });
}
};
renderOptGroup = (searchVal) => {
let { noOptionsPlaceholder, onSelectOption } = this.props;
this.filterOptions = this.props.getFilterOptions(searchVal);
if (this.filterOptions.length === 0) {
return (
<div className="none-search-result">{noOptionsPlaceholder}</div>
);
}
return this.filterOptions.map((opt, i) => {
let key = opt.value.column ? opt.value.column.key : i;
let isActive = this.state.activeIndex === i;
return (
<Option
key={key}
index={i}
isActive={isActive}
value={opt.value}
onSelectOption={onSelectOption}
changeIndex={this.changeIndex}
supportMultipleSelect={this.props.supportMultipleSelect}
disableHover={this.state.disableHover}
>
{opt.label}
</Option>
);
});
};
render() {
const { searchable, searchPlaceholder, top, left, minWidth, value, isShowSelected, isInModal, position,
className, addOptionAble, component } = this.props;
const { AddOption } = component || {};
let { searchVal } = this.state;
let style = { top: top || 0, left: left || 0 };
if (minWidth) {
style = { top: top || 0, left: left || 0, minWidth };
}
if (isInModal) {
style = {
position: 'fixed',
left: position.x,
top: position.y + position.height,
minWidth: position.width,
opacity: 0,
};
}
return (
<ClickOutside onClickOutside={this.props.onClickOutside}>
<div
className={classnames('seafile-option-group', className ? 'seafile-option-group-' + className : '', {
'pt-0': isShowSelected,
'create-new-seafile-option-group': addOptionAble,
})}
ref={(ref) => this.optionGroupRef = ref}
style={style}
onMouseDown={this.onMouseDown}
>
{isShowSelected &&
<div className="editor-list-delete mb-2" onClick={(e) => e.stopPropagation()}>{value.label || ''}</div>
}
{searchable && (
<div className="seafile-option-group-search">
<SearchInput
className="option-search-control"
placeholder={searchPlaceholder}
onChange={this.onChangeSearch}
autoFocus={true}
/>
</div>
)}
<div className="seafile-option-group-content" ref={(ref) => this.optionGroupContentRef = ref}>
{this.renderOptGroup(searchVal)}
</div>
{addOptionAble && AddOption}
</div>
</ClickOutside>
);
}
}
SelectOptionGroup.propTypes = {
top: PropTypes.number,
left: PropTypes.number,
minWidth: PropTypes.number,
options: PropTypes.array,
onSelectOption: PropTypes.func,
searchable: PropTypes.bool,
addOptionAble: PropTypes.bool,
component: PropTypes.object,
searchPlaceholder: PropTypes.string,
noOptionsPlaceholder: PropTypes.string,
onClickOutside: PropTypes.func.isRequired,
closeSelect: PropTypes.func.isRequired,
getFilterOptions: PropTypes.func.isRequired,
supportMultipleSelect: PropTypes.bool,
value: PropTypes.object,
isShowSelected: PropTypes.bool,
stopClickEvent: PropTypes.bool,
isInModal: PropTypes.bool,
position: PropTypes.object,
className: PropTypes.string,
};
export default SelectOptionGroup;

View File

@ -0,0 +1,50 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
class Option extends Component {
onSelectOption = (value, event) => {
if (this.props.supportMultipleSelect) {
event.stopPropagation();
}
this.props.onSelectOption(value, event);
};
onMouseEnter = () => {
if (!this.props.disableHover) {
this.props.changeIndex(this.props.index);
}
};
onMouseLeave = () => {
if (!this.props.disableHover) {
this.props.changeIndex(-1);
}
};
render() {
return (
<div
className={classnames('seafile-select-option', { 'seafile-select-option-active': this.props.isActive })}
onClick={this.onSelectOption.bind(this, this.props.value)}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>{this.props.children}
</div>
);
}
}
Option.propTypes = {
index: PropTypes.number,
isActive: PropTypes.bool,
changeIndex: PropTypes.func,
value: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
children: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
onSelectOption: PropTypes.func,
supportMultipleSelect: PropTypes.bool,
disableHover: PropTypes.bool,
};
export default Option;

View File

@ -89,7 +89,7 @@ Picker.propTypes = {
showHourAndMinute: PropTypes.bool.isRequired,
disabledDate: PropTypes.func.isRequired,
value: PropTypes.object,
disabled: PropTypes.func.isRequired,
disabled: PropTypes.func,
inputWidth: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Button, Form, FormGroup, Label, Input, Modal, ModalBody, ModalFooter, Alert } from 'reactstrap';
import { gettext } from '../../utils/constants';
import { SeahubSelect } from '../common/select';
import { seafileAPI } from '../../utils/seafile-api';
import toaster from '../toast';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
@ -24,6 +25,12 @@ class AddAbuseReportDialog extends React.Component {
reporter: this.props.contactEmail,
errMessage: '',
};
this.typeOptions = [
{ value: 'copyright', label: gettext('Copyright Infringement') },
{ value: 'virus', label: gettext('Virus') },
{ value: 'abuse_content', label: gettext('Abuse Content') },
{ value: 'other', label: gettext('Other') },
];
}
onAbuseReport = () => {
@ -45,8 +52,8 @@ class AddAbuseReportDialog extends React.Component {
});
};
onAbuseTypeChange = (event) => {
let type = event.target.value;
onAbuseTypeChange = (option) => {
let type = option.value;
if (type === this.state.abuseType) {
return;
}
@ -70,13 +77,13 @@ class AddAbuseReportDialog extends React.Component {
<ModalBody>
<Form>
<FormGroup>
<Label for="abuse-type-select">{gettext('Abuse Type')}</Label>
<Input type="select" id="abuse-type-select" onChange={(event) => this.onAbuseTypeChange(event)}>
<option value='copyright'>{gettext('Copyright Infringement')}</option>
<option value='virus'>{gettext('Virus')}</option>
<option value='abuse_content'>{gettext('Abuse Content')}</option>
<option value='other'>{gettext('Other')}</option>
</Input>
<Label>{gettext('Abuse Type')}</Label>
<SeahubSelect
options={this.typeOptions}
value={this.typeOptions.find(option => option.value === this.state.abuseType) || this.typeOptions[0]}
onChange={this.onAbuseTypeChange}
isClearable={false}
/>
</FormGroup>
<FormGroup>
<Label>{gettext('Contact Information')}</Label>

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Modal, ModalBody, ModalFooter, Input, Button } from 'reactstrap';
import { Modal, ModalBody, ModalFooter, Label, Input, Button } from 'reactstrap';
import { Utils } from '../../utils/utils';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
@ -68,7 +68,7 @@ class CreateGroupDialog extends React.Component {
<Modal isOpen={true} toggle={this.props.toggleDialog} autoFocus={false}>
<SeahubModalHeader toggle={this.props.toggleDialog}>{gettext('New Group')}</SeahubModalHeader>
<ModalBody>
<label htmlFor="groupName">{gettext('Name')}</label>
<Label for="groupName">{gettext('Name')}</Label>
<Input
type="text"
id="groupName"

View File

@ -117,8 +117,7 @@ class CreateRepoDialog extends React.Component {
return true;
}
onPermissionChange = (e) => {
let permission = e.target.value;
onPermissionChange = (permission) => {
this.setState({ permission: permission });
};
@ -223,11 +222,19 @@ class CreateRepoDialog extends React.Component {
{this.props.libraryType === 'group' && (
<FormGroup>
<Label for="exampleSelect">{gettext('Permission')}</Label>
<Input type="select" name="select" id="exampleSelect" onChange={this.onPermissionChange} value={this.state.permission}>
<option value='rw'>{gettext('Read-Write')}</option>
<option value='r'>{gettext('Read-Only')}</option>
</Input>
<Label>{gettext('Permission')}</Label>
<SeahubSelect
options={[
{ value: 'rw', label: gettext('Read-Write') },
{ value: 'r', label: gettext('Read-Only') }
]}
onChange={selectedOption => this.onPermissionChange(selectedOption.value)}
value={{
value: this.state.permission,
label: this.state.permission === 'rw' ? gettext('Read-Write') : gettext('Read-Only')
}}
isClearable={false}
/>
</FormGroup>
)}
{enableEncryptedLibrary &&

View File

@ -1,126 +0,0 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Button, ModalBody, ModalFooter, Input } from 'reactstrap';
import { gettext } from '../../utils/constants';
import { TAG_COLORS } from '../../constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
const propTypes = {
repoID: PropTypes.string.isRequired,
onRepoTagCreated: PropTypes.func,
toggleCancel: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired
};
class CreateTagDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
tagName: '',
tagColor: TAG_COLORS[0],
newTag: {},
errorMsg: '',
};
}
inputNewName = (e) => {
this.setState({
tagName: e.target.value,
});
if (this.state.errorMsg) {
this.setState({ errorMsg: '' });
}
};
selectTagcolor = (e) => {
this.setState({
tagColor: e.target.value,
});
};
createTag = () => {
let name = this.state.tagName;
let color = this.state.tagColor;
let repoID = this.props.repoID;
seafileAPI.createRepoTag(repoID, name, color).then((res) => {
let repoTagID = res.data.repo_tag.repo_tag_id;
if (this.props.onRepoTagCreated) this.props.onRepoTagCreated(repoTagID);
this.props.toggleCancel();
}).catch((error) => {
let errMessage;
if (error.response.status === 500) {
errMessage = gettext('Internal Server Error');
} else if (error.response.status === 400) {
errMessage = gettext('Tag "{name}" already exists.');
errMessage = errMessage.replace('{name}', Utils.HTMLescape(name));
}
this.setState({ errorMsg: errMessage });
});
};
handleKeyDown = (e) => {
if (e.key === 'Enter') {
this.createTag();
}
};
render() {
let canSave = this.state.tagName.trim() ? true : false;
return (
<Fragment>
<SeahubModalHeader toggle={this.props.onClose}>
<span className="tag-dialog-back sf3-font sf3-font-arrow rotate-180 d-inline-block" onClick={this.props.toggleCancel} aria-label={gettext('Back')}></span>
{gettext('New Tag')}
</SeahubModalHeader>
<ModalBody>
<div role="form" className="tag-create">
<div className="form-group">
<label className="form-label">{gettext('Name')}</label>
<Input
name="tag-name"
onKeyDown={this.handleKeyDown}
autoFocus={true}
value={this.state.tagName}
onChange={this.inputNewName}
/>
<div className="mt-2"><span className="error">{this.state.errorMsg}</span></div>
</div>
<div className="form-group">
<label className="form-label">{gettext('Select a color')}</label>
<div className="d-flex justify-content-between">
{TAG_COLORS.map((item, index) => {
return (
<div key={index} className="tag-color-option" onChange={this.selectTagcolor}>
<label className="colorinput">
{index === 0 ?
<input name="color" type="radio" value={item} className="colorinput-input" defaultChecked onClick={this.selectTagcolor}></input> :
<input name="color" type="radio" value={item} className="colorinput-input" onClick={this.selectTagcolor}></input>}
<span className="colorinput-color rounded-circle d-flex align-items-center justify-content-center" style={{ backgroundColor: item }}>
<i className="sf2-icon-tick color-selected"></i>
</span>
</label>
</div>
);
})
}
</div>
</div>
</div>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggleCancel}>{gettext('Cancel')}</Button>
{canSave ?
<Button color="primary" onClick={this.createTag}>{gettext('Save')}</Button> :
<Button color="primary" disabled>{gettext('Save')}</Button>
}
</ModalFooter>
</Fragment>
);
}
}
CreateTagDialog.propTypes = propTypes;
export default CreateTagDialog;

View File

@ -5,19 +5,7 @@ import { gettext } from '../../../utils/constants';
import Loading from '../../loading';
import OpIcon from '../../op-icon';
const propTypes = {
mode: PropTypes.string,
permission: PropTypes.object,
onChangeMode: PropTypes.func.isRequired,
onUpdateCustomPermission: PropTypes.func.isRequired,
};
class CustomPermissionEditor extends React.Component {
static defaultProps = {
mode: 'add'
};
constructor(props) {
super(props);
this.state = {
@ -50,7 +38,6 @@ class CustomPermissionEditor extends React.Component {
} else {
this.setState({ isLoading: false });
}
}
onChangePermissionName = (evt) => {
@ -108,12 +95,9 @@ class CustomPermissionEditor extends React.Component {
};
render() {
const { mode } = this.props;
const { mode = 'add' } = this.props;
const title = mode === 'add' ? gettext('Add permission') : gettext('Edit permission');
const { isLoading, permission_name, permission_desc, permission, errMessage } = this.state;
return (
<div className="custom-permission">
<div className="permission-header">
@ -212,6 +196,11 @@ class CustomPermissionEditor extends React.Component {
}
CustomPermissionEditor.propTypes = propTypes;
CustomPermissionEditor.propTypes = {
mode: PropTypes.string,
permission: PropTypes.object,
onChangeMode: PropTypes.func.isRequired,
onUpdateCustomPermission: PropTypes.func.isRequired,
};
export default CustomPermissionEditor;

View File

@ -1,12 +1,12 @@
import React, { Fragment, } from 'react';
import PropTypes from 'prop-types';
import { gettext, isOrgContext, username } from '../../utils/constants';
import { Modal, ModalBody } from 'reactstrap';
import { gettext, isOrgContext, username } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api.js';
import { Utils } from '../../utils/utils';
import toaster from '../../components/toast';
import EmptyTip from '../../components/empty-tip';
import Loading from '../../components/loading';
import toaster from '../toast';
import EmptyTip from '../empty-tip';
import Loading from '../loading';
import Department from '../../models/department';
import SeahubModalHeader from '../common/seahub-modal-header';
import DepartmentGroup from './department-detail-widget/department-group';

View File

@ -2,8 +2,8 @@ import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Tooltip } from 'reactstrap';
import { gettext, mediaUrl } from '../../../utils/constants';
import EmptyTip from '../../../components/empty-tip';
import Loading from '../../../components/loading';
import EmptyTip from '../../empty-tip';
import Loading from '../../loading';
const ItemPropTypes = {
member: PropTypes.object,

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Loading from '../../../components/loading';
import Loading from '../../loading';
import { isOrgContext } from '../../../utils/constants';
const ItemPropTypes = {

View File

@ -9,14 +9,11 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header';
class DismissGroupDialog extends React.Component {
constructor(props) {
super(props);
}
dismissGroup = () => {
let that = this;
seafileAPI.deleteGroup(this.props.groupID).then((res) => {
that.props.onGroupChanged();
const { groupID } = this.props;
seafileAPI.deleteGroup(groupID).then((res) => {
this.props.onGroupDeleted();
toaster.success(gettext('Group deleted'));
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
@ -25,13 +22,13 @@ class DismissGroupDialog extends React.Component {
render() {
return (
<Modal isOpen={this.props.showDismissGroupDialog} toggle={this.props.toggleDismissGroupDialog}>
<SeahubModalHeader>{gettext('Delete Group')}</SeahubModalHeader>
<Modal isOpen={true} toggle={this.props.toggleDialog}>
<SeahubModalHeader toggle={this.props.toggleDialog}>{gettext('Delete Group')}</SeahubModalHeader>
<ModalBody>
<span>{gettext('Really want to delete this group?')}</span>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggleDismissGroupDialog}>{gettext('Cancel')}</Button>
<Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.dismissGroup}>{gettext('Delete')}</Button>
</ModalFooter>
</Modal>
@ -40,11 +37,9 @@ class DismissGroupDialog extends React.Component {
}
const DismissGroupDialogPropTypes = {
showDismissGroupDialog: PropTypes.bool.isRequired,
toggleDismissGroupDialog: PropTypes.func.isRequired,
loadGroup: PropTypes.func.isRequired,
groupID: PropTypes.string,
onGroupChanged: PropTypes.func.isRequired,
groupID: PropTypes.number.isRequired,
toggleDialog: PropTypes.func.isRequired,
onGroupDeleted: PropTypes.func.isRequired
};
DismissGroupDialog.propTypes = DismissGroupDialogPropTypes;

View File

@ -1,220 +0,0 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalBody, ModalFooter } from 'reactstrap';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import CreateTagDialog from './create-tag-dialog';
import toaster from '../toast';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
require('../../css/repo-tag.css');
const TagItemPropTypes = {
repoID: PropTypes.string.isRequired,
repoTag: PropTypes.object.isRequired,
filePath: PropTypes.string.isRequired,
fileTagList: PropTypes.array.isRequired,
onFileTagChanged: PropTypes.func.isRequired,
};
class TagItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isTagHighlighted: false
};
}
onMouseEnter = () => {
this.setState({
isTagHighlighted: true
});
};
onMouseLeave = () => {
this.setState({
isTagHighlighted: false
});
};
getRepoTagIdList = () => {
let repoTagIdList = [];
let fileTagList = this.props.fileTagList || [];
repoTagIdList = fileTagList.map((fileTag) => fileTag.repo_tag_id);
return repoTagIdList;
};
onEditFileTag = () => {
let { repoID, repoTag, filePath } = this.props;
let repoTagIdList = this.getRepoTagIdList();
if (repoTagIdList.indexOf(repoTag.id) === -1) {
let id = repoTag.id;
seafileAPI.addFileTag(repoID, filePath, id).then(() => {
repoTagIdList = this.getRepoTagIdList();
this.props.onFileTagChanged();
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
} else {
let fileTag = null;
let fileTagList = this.props.fileTagList;
for (let i = 0; i < fileTagList.length; i++) {
if (fileTagList[i].repo_tag_id === repoTag.id) {
fileTag = fileTagList[i];
break;
}
}
seafileAPI.deleteFileTag(repoID, fileTag.id).then(() => {
repoTagIdList = this.getRepoTagIdList();
this.props.onFileTagChanged();
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
};
render() {
const { isTagHighlighted } = this.state;
const { repoTag } = this.props;
const repoTagIdList = this.getRepoTagIdList();
const isTagSelected = repoTagIdList.indexOf(repoTag.id) != -1;
return (
<li
className={`tag-list-item cursor-pointer px-4 d-flex justify-content-between align-items-center ${isTagHighlighted ? 'hl' : ''}`}
onClick={this.onEditFileTag}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>
<div className="d-flex align-items-center">
<span className="tag-color w-4 h-4 rounded-circle" style={{ backgroundColor: repoTag.color }}></span>
<span className="tag-name mx-2">{repoTag.name}</span>
</div>
{isTagSelected && <i className="sf2-icon-tick tag-selected-icon"></i>}
</li>
);
}
}
TagItem.propTypes = TagItemPropTypes;
const TagListPropTypes = {
repoID: PropTypes.string.isRequired,
repoTags: PropTypes.array.isRequired,
filePath: PropTypes.string.isRequired,
fileTagList: PropTypes.array.isRequired,
onFileTagChanged: PropTypes.func.isRequired,
toggleCancel: PropTypes.func.isRequired,
createNewTag: PropTypes.func.isRequired,
};
class TagList extends React.Component {
render() {
const { repoTags } = this.props;
return (
<Fragment>
<SeahubModalHeader toggle={this.props.toggleCancel}>{gettext('Select Tags')}</SeahubModalHeader>
<ModalBody className="px-0">
<ul className="tag-list tag-list-container">
{repoTags.map((repoTag) => {
return (
<TagItem
key={repoTag.id}
repoTag={repoTag}
repoID={this.props.repoID}
filePath={this.props.filePath}
fileTagList={this.props.fileTagList}
onFileTagChanged={this.props.onFileTagChanged}
/>
);
})}
</ul>
<a
href="#"
className="add-tag-link px-4 py-2 d-flex align-items-center"
onClick={this.props.createNewTag}
>
<span className="sf2-icon-plus mr-2"></span>
{gettext('Create a new tag')}
</a>
</ModalBody>
<ModalFooter>
<Button onClick={this.props.toggleCancel}>{gettext('Close')}</Button>
</ModalFooter>
</Fragment>
);
}
}
TagList.propTypes = TagListPropTypes;
const propTypes = {
repoID: PropTypes.string.isRequired,
repoTags: PropTypes.array.isRequired,
filePath: PropTypes.string.isRequired,
fileTagList: PropTypes.array.isRequired,
toggleCancel: PropTypes.func.isRequired,
onFileTagChanged: PropTypes.func.isRequired,
};
class EditFileTagDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
isCreateRepoTagShow: false,
isListRepoTagShow: true,
};
}
createNewTag = () => {
this.setState({
isCreateRepoTagShow: !this.state.isCreateRepoTagShow,
isListRepoTagShow: !this.state.isListRepoTagShow,
});
};
onRepoTagCreated = (repoTagID) => {
let { repoID, filePath } = this.props;
seafileAPI.addFileTag(repoID, filePath, repoTagID).then(() => {
this.props.onFileTagChanged();
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
render() {
return (
<Modal isOpen={true} toggle={this.props.toggleCancel} autoFocus={false}>
{this.state.isListRepoTagShow &&
<TagList
repoID={this.props.repoID}
repoTags={this.props.repoTags}
filePath={this.props.filePath}
fileTagList={this.props.fileTagList}
onFileTagChanged={this.props.onFileTagChanged}
toggleCancel={this.props.toggleCancel}
createNewTag={this.createNewTag}
/>
}
{this.state.isCreateRepoTagShow &&
<CreateTagDialog
repoID={this.props.repoID}
onClose={this.props.toggleCancel}
toggleCancel={this.createNewTag}
onRepoTagCreated={this.onRepoTagCreated}
/>
}
</Modal>
);
}
}
EditFileTagDialog.propTypes = propTypes;
export default EditFileTagDialog;

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import copy from 'copy-to-clipboard';
import dayjs from 'dayjs';
import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, Alert } from 'reactstrap';
import { Button, Form, FormGroup, Label, Input, InputGroup, Alert } from 'reactstrap';
import { gettext, shareLinkForceUsePassword, shareLinkPasswordMinLength, shareLinkPasswordStrengthLevel, canSendShareLinkEmail, uploadLinkExpireDaysMin, uploadLinkExpireDaysMax, uploadLinkExpireDaysDefault } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
@ -300,14 +300,12 @@ class GenerateUploadLink extends React.Component {
<Input type="text" readOnly={true} value={sharedUploadInfo.password} /> :
<Input type="text" readOnly={true} value={'***************'} />
}
<InputGroupAddon addonType="append">
<Button
aria-label={this.state.storedPasswordVisible ? gettext('Hide') : gettext('Show')}
onClick={this.toggleStoredPasswordVisible}
className={`link-operation-icon eye-icon sf3-font sf3-font-eye${this.state.storedPasswordVisible ? '' : '-slash'}`}
>
</Button>
</InputGroupAddon>
<Button
aria-label={this.state.storedPasswordVisible ? gettext('Hide') : gettext('Show')}
onClick={this.toggleStoredPasswordVisible}
className={`link-operation-icon eye-icon sf3-font sf3-font-eye${this.state.storedPasswordVisible ? '' : '-slash'}`}
>
</Button>
</InputGroup>
</dd>
</>
@ -338,15 +336,13 @@ class GenerateUploadLink extends React.Component {
) : (
<InputGroup className="share-link-details-item">
<Input type="text" readOnly={true} value={dayjs(sharedUploadInfo.expire_date).format('YYYY-MM-DD HH:mm:ss')} />
<InputGroupAddon addonType="append">
<Button
aria-label={gettext('Edit')}
title={gettext('Edit')}
className="link-operation-icon sf3-font sf3-font-rename"
onClick={this.editExpirationToggle}
>
</Button>
</InputGroupAddon>
<Button
aria-label={gettext('Edit')}
title={gettext('Edit')}
className="link-operation-icon sf3-font sf3-font-rename"
onClick={this.editExpirationToggle}
>
</Button>
</InputGroup>
)}
</dd>
@ -389,14 +385,12 @@ class GenerateUploadLink extends React.Component {
<span className="tip">{passwordLengthTip}</span>
<InputGroup style={{ width: inputWidth }}>
<Input id="passwd" type={this.state.passwordVisible ? 'text' : 'password'} value={this.state.password || ''} onChange={this.inputPassword} />
<InputGroupAddon addonType="append">
<Button onClick={this.togglePasswordVisible}>
<i className={`link-operation-icon sf3-font sf3-font-eye${this.state.passwordVisible ? '' : '-slash'}`}></i>
</Button>
<Button onClick={this.generatePassword}>
<i className="link-operation-icon sf3-font sf3-font-magic"></i>
</Button>
</InputGroupAddon>
<Button onClick={this.togglePasswordVisible}>
<i className={`link-operation-icon sf3-font sf3-font-eye${this.state.passwordVisible ? '' : '-slash'}`}></i>
</Button>
<Button onClick={this.generatePassword}>
<i className="link-operation-icon sf3-font sf3-font-magic"></i>
</Button>
</InputGroup>
</FormGroup>
<FormGroup>

View File

@ -0,0 +1,111 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalBody, Label } from 'reactstrap';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
import copy from 'copy-to-clipboard';
import toaster from '../toast';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import '../../css/group-invite-members-dialog.css';
const propTypes = {
groupID: PropTypes.number.isRequired,
toggleInviteMembersDialog: PropTypes.func.isRequired,
};
class GroupInviteMembersDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
inviteList: [],
};
}
componentDidMount() {
this.listInviteLinks();
}
listInviteLinks = () => {
seafileAPI.getGroupInviteLinks(this.props.groupID).then((res) => {
this.setState({ inviteList: res.data.group_invite_link_list });
}).catch(error => {
this.onError(error);
});
};
addInviteLink = () => {
seafileAPI.addGroupInviteLinks(this.props.groupID).then(() => {
this.listInviteLinks();
}).catch(error => {
this.onError(error);
});
};
deleteLink = (token) => {
seafileAPI.deleteGroupInviteLinks(this.props.groupID, token).then(() => {
this.listInviteLinks();
}).catch(error => {
this.onError(error);
});
};
onError = (error) => {
let errMsg = Utils.getErrorMsg(error, true);
if (!error.response || error.response.status !== 403) {
toaster.danger(errMsg);
}
};
copyLink = () => {
const inviteLinkItem = this.state.inviteList[0];
copy(inviteLinkItem.link);
const message = gettext('Invitation link has been copied to clipboard');
toaster.success((message), {
duration: 2
});
};
toggle = () => {
this.props.toggleInviteMembersDialog();
};
render() {
const { inviteList } = this.state;
const link = inviteList[0];
return (
<Modal isOpen={true} toggle={this.toggle} className="group-invite-members">
<SeahubModalHeader toggle={this.toggle}>{gettext('Invite members')}</SeahubModalHeader>
<ModalBody>
{link ?
<>
<Label>{gettext('Group invitation link')}</Label>
<div className="invite-link-item">
<div className="form-item text-truncate">{link.link}</div>
<div className="invite-link-copy">
<Button color="primary" onClick={this.copyLink} className="invite-link-copy-btn text-truncate">{gettext('Copy')}</Button>
</div>
<Button color="primary" outline onClick={this.deleteLink.bind(this, link.token)} className="delete-link-btn ml-2">
<i className="sf3-font-delete1 sf3-font"></i>
</Button>
</div>
</>
:
<>
<div className="no-link-tip mb-4">
{gettext('No group invitation link yet. Group invitation link let registered users to join the group by clicking a link.')}
</div>
<Button color="primary" onClick={this.addInviteLink} className="my-4">{gettext('Generate')}</Button>
</>
}
</ModalBody>
</Modal>
);
}
}
GroupInviteMembersDialog.propTypes = propTypes;
export default GroupInviteMembersDialog;

View File

@ -9,7 +9,7 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header';
import Loading from '../loading';
const propTypes = {
groupID: PropTypes.string.isRequired,
groupID: PropTypes.number.isRequired,
toggleDialog: PropTypes.func.isRequired
};

View File

@ -0,0 +1,100 @@
.lightbox-side-panel {
width: 10px;
height: calc(100% - 100px);
display: flex;
flex-direction: column;
top: 50px;
right: 0;
position: absolute;
font-size: 1rem;
color: #fff;
background-color: #333;
border: 1px solid #666;
transition: width 0.3s ease;
}
.lightbox-side-panel .cur-view-detail {
background-color: inherit;
border: none;
}
.lightbox-side-panel .side-panel-controller {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 30px;
left: -40px;
width: 40px;
height: 48px;
background-color: #333;
color: #fff;
border: 1px solid #666;
border-right: none;
border-top-left-radius: 50%;
border-bottom-left-radius: 50%;
margin-right: -1px;
cursor: pointer;
}
.lightbox-side-panel .side-panel-controller .expand-button {
width: 32px;
height: 32px;
opacity: 0.7;
border: none;
}
.lightbox-side-panel .side-panel-controller:hover .expand-button {
opacity: 1;
}
.lightbox-side-panel .detail-header {
width: 100%;
height: fit-content;
display: flex;
padding: 20px 16px;
border: none;
font-size: 1rem;
}
.lightbox-side-panel .detail-header .detail-title .name,
.lightbox-side-panel .file-details-collapse .file-details-collapse-header .file-details-collapse-header-title,
.lightbox-side-panel .dirent-detail-item .dirent-detail-item-name,
.lightbox-side-panel .sf-metadata-number-property-detail-editor {
color: #fff;
}
.lightbox-side-panel .detail-body {
scrollbar-color: #666 #333;
padding: 0 16px 8px;
}
.lightbox-side-panel .sf-metadata-ui.collaborator-item,
.lightbox-side-panel .sf-metadata-text-property-detail-editor:not(.formatter),
.lightbox-side-panel .sf-metadata-number-property-detail-editor:focus {
color: #212529;
}
.lightbox-side-panel .sf-metadata-text-property-detail-editor:not(.formatter) {
cursor: text;
}
.lightbox-side-panel .dirent-detail-item .dirent-detail-item-value:not(.editable) .sf-metadata-record-cell-empty:empty::before,
.lightbox-side-panel .sf-metadata-property-detail-editor:empty::before,
.lightbox-side-panel .sf-metadata-property-detail-capture-information-item .dirent-detail-item-value:empty::before,
.lightbox-side-panel .file-details-collapse .file-details-collapse-header .sf3-font-down,
.lightbox-side-panel .sf-metadata-number-property-detail-editor::placeholder {
color: #999;
}
.lightbox-side-panel .file-details-collapse .file-details-collapse-header .file-details-collapse-header-operation:hover,
.lightbox-side-panel .dirent-detail-item .dirent-detail-item-value.editable:hover {
background-color: #666;
}
.lightbox-side-panel .detail-body .dirent-detail-people {
position: relative;
transform: none;
padding-bottom: 16px;
border-bottom: 1px solid #999;
}

View File

@ -1,13 +1,21 @@
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import { gettext } from '../../../utils/constants';
import Lightbox from '@seafile/react-image-lightbox';
import { useMetadataAIOperations } from '../../hooks/metadata-ai-operation';
import { SYSTEM_FOLDERS } from '../../constants';
import { useMetadataAIOperations } from '../../../hooks/metadata-ai-operation';
import EmbeddedFileDetails from '../../dirent-detail/embedded-file-details';
import { SYSTEM_FOLDERS } from '../../../constants';
import Icon from '../../icon';
import '@seafile/react-image-lightbox/style.css';
import './index.css';
const SIDE_PANEL_COLLAPSED_WIDTH = 10;
const SIDE_PANEL_EXPANDED_WIDTH = 300;
const ImageDialog = ({ repoID, repoInfo, enableRotate: oldEnableRotate = true, imageItems, imageIndex, closeImagePopup, moveToPrevImage, moveToNextImage, onDeleteImage, onRotateImage, isCustomPermission }) => {
const [expanded, setExpanded] = useState(false);
const ImageDialog = ({ enableRotate: oldEnableRotate, imageItems, imageIndex, closeImagePopup, moveToPrevImage, moveToNextImage, onDeleteImage, onRotateImage }) => {
const { enableOCR, enableMetadata, canModify, onOCR: onOCRAPI, OCRSuccessCallBack } = useMetadataAIOperations();
const downloadImage = useCallback((url) => {
@ -18,18 +26,23 @@ const ImageDialog = ({ enableRotate: oldEnableRotate, imageItems, imageIndex, cl
window.open(imageItems[imageIndex].url, '_blank');
}, [imageItems, imageIndex]);
const onToggleSidePanel = useCallback(() => {
setExpanded(!expanded);
}, [expanded]);
const imageItemsLength = imageItems.length;
if (imageItemsLength === 0) return null;
const id = imageItems[imageIndex].id;
const name = imageItems[imageIndex].name;
const mainImg = imageItems[imageIndex];
const nextImg = imageItems[(imageIndex + 1) % imageItemsLength];
const prevImg = imageItems[(imageIndex + imageItemsLength - 1) % imageItemsLength];
// The backend server does not support rotating HEIC images
// The backend server does not support rotating HEIC, GIF, SVG images
let enableRotate = oldEnableRotate;
const urlParts = mainImg.src.split('?')[0].split('.');
const suffix = urlParts[urlParts.length - 1];
if (suffix === 'heic') {
const suffix = urlParts[urlParts.length - 1].toLowerCase();
if (suffix === 'heic' || suffix === 'svg' || suffix === 'gif') {
enableRotate = false;
}
@ -39,6 +52,28 @@ const ImageDialog = ({ enableRotate: oldEnableRotate, imageItems, imageIndex, cl
onOCR = () => onOCRAPI({ parentDir: mainImg.parentDir, fileName: mainImg.name }, { success_callback: OCRSuccessCallBack });
}
const renderSidePanel = () => {
return (
<div
className="lightbox-side-panel"
style={{ width: expanded ? SIDE_PANEL_EXPANDED_WIDTH : SIDE_PANEL_COLLAPSED_WIDTH }}
aria-expanded={expanded}
>
<div className="side-panel-controller" onClick={onToggleSidePanel}>
<Icon className="expand-button" symbol={expanded ? 'right_arrow' : 'left_arrow'} />
</div>
{expanded &&
<EmbeddedFileDetails
repoID={repoID}
repoInfo={repoInfo}
path={mainImg.parentDir}
dirent={{ id, name, type: 'file' }}
/>
}
</div>
);
};
return (
<Lightbox
wrapperClassName='custom-image-previewer'
@ -64,6 +99,10 @@ const ImageDialog = ({ enableRotate: oldEnableRotate, imageItems, imageIndex, cl
onRotateImage={(onRotateImage && enableRotate) ? (angle) => onRotateImage(imageIndex, angle) : null}
onOCR={onOCR}
OCRLabel={gettext('OCR')}
sidePanel={!isCustomPermission ? {
render: renderSidePanel,
width: expanded ? SIDE_PANEL_EXPANDED_WIDTH : SIDE_PANEL_COLLAPSED_WIDTH,
} : null}
/>
);
};
@ -79,8 +118,4 @@ ImageDialog.propTypes = {
enableRotate: PropTypes.bool,
};
ImageDialog.defaultProps = {
enableRotate: true,
};
export default ImageDialog;

View File

@ -5,7 +5,7 @@ import { gettext, siteRoot, groupImportMembersExtraMsg } from '../../utils/const
import SeahubModalHeader from '@/components/common/seahub-modal-header';
const propTypes = {
toggleImportMembersDialog: PropTypes.func.isRequired,
toggleDialog: PropTypes.func.isRequired,
importMembersInBatch: PropTypes.func.isRequired,
};
@ -19,7 +19,7 @@ class ImportMembersDialog extends React.Component {
}
toggle = () => {
this.props.toggleImportMembersDialog();
this.props.toggleDialog();
};
openFileInput = () => {
@ -49,9 +49,8 @@ class ImportMembersDialog extends React.Component {
return (
<Modal isOpen={true} toggle={this.toggle}>
<SeahubModalHeader toggle={this.toggle}>{gettext('Import members from a .xlsx file')}</SeahubModalHeader>
<ModalBody>
<p>{groupImportMembersExtraMsg}</p>
{groupImportMembersExtraMsg && <p>{groupImportMembersExtraMsg}</p>}
<p><a className="text-secondary small" href={`${siteRoot}api/v2.1/group-members-import-example/`}>{gettext('Download an example file')}</a></p>
<button className="btn btn-outline-primary" onClick={this.openFileInput}>{gettext('Upload file')}</button>
<input className="d-none" type="file" onChange={this.uploadFile} ref={this.fileInputRef} />

View File

@ -1,94 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalBody, ModalFooter } from 'reactstrap';
import { gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import FileChooser from '../file-chooser';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
import '../../css/insert-repo-image-dialog.css';
const { siteRoot, serviceUrl } = window.app.config;
const propTypes = {
repoID: PropTypes.string.isRequired,
filePath: PropTypes.string.isRequired,
toggleCancel: PropTypes.func.isRequired,
};
class InsertRepoImageDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
repo: null,
selectedPath: '',
};
}
insertImage = () => {
const url = serviceUrl + '/lib/' + this.state.repo.repo_id + '/file' + Utils.encodePath(this.state.selectedPath) + '?raw=1';
window.richMarkdownEditor.onInsertImage(url);
this.props.toggleCancel();
};
onDirentItemClick = (repo, selectedPath, dirent) => {
if (dirent.type === 'file' && Utils.imageCheck(dirent.name)) {
this.setState({
repo: repo,
selectedPath: selectedPath,
});
}
else {
this.setState({ repo: null, selectedPath: '' });
}
};
onRepoItemClick = () => {
this.setState({ repo: null, selectedPath: '' });
};
render() {
const toggle = this.props.toggleCancel;
const fileSuffixes = ['jpg', 'png', 'jpeg', 'gif', 'bmp'];
let imageUrl;
if (this.state.repo) {
imageUrl = siteRoot + 'thumbnail/' + this.state.repo.repo_id + '/1024' + this.state.selectedPath;
}
return (
<Modal isOpen={true} toggle={toggle} size='lg'>
<SeahubModalHeader toggle={toggle}>{gettext('Select Image')}</SeahubModalHeader>
<ModalBody>
<div className="d-flex">
<div className="col-6">
<FileChooser
isShowFile={true}
repoID={this.props.repoID}
onDirentItemClick={this.onDirentItemClick}
onRepoItemClick={this.onRepoItemClick}
mode="current_repo_and_other_repos"
fileSuffixes={fileSuffixes}
/>
</div>
<div className="insert-image-container col-6">
{imageUrl ?
<img src={imageUrl} className='d-inline-block mh-100 mw-100' alt=''/> :
<span>{gettext('No preview')}</span>
}
</div>
</div>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={toggle}>{gettext('Cancel')}</Button>
{this.state.selectedPath ?
<Button color="primary" onClick={this.insertImage}>{gettext('Submit')}</Button>
: <Button color="primary" disabled>{gettext('Submit')}</Button>
}
</ModalFooter>
</Modal>
);
}
}
InsertRepoImageDialog.propTypes = propTypes;
export default InsertRepoImageDialog;

View File

@ -9,13 +9,10 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header';
class LeaveGroupDialog extends React.Component {
constructor(props) {
super(props);
}
leaveGroup = () => {
seafileAPI.quitGroup(this.props.groupID, username).then((res) => {
this.props.onGroupChanged();
const { groupID } = this.props;
seafileAPI.quitGroup(groupID, username).then((res) => {
this.props.onLeavingGroup();
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
@ -24,13 +21,13 @@ class LeaveGroupDialog extends React.Component {
render() {
return (
<Modal isOpen={true} toggle={this.props.toggleLeaveGroupDialog}>
<SeahubModalHeader toggle={this.props.toggleLeaveGroupDialog}>{gettext('Leave Group')}</SeahubModalHeader>
<Modal isOpen={true} toggle={this.props.toggleDialog}>
<SeahubModalHeader toggle={this.props.toggleDialog}>{gettext('Leave Group')}</SeahubModalHeader>
<ModalBody>
<p>{gettext('Really want to leave this group?')}</p>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggleLeaveGroupDialog}>{gettext('Cancel')}</Button>
<Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.leaveGroup}>{gettext('Leave')}</Button>
</ModalFooter>
</Modal>
@ -39,9 +36,9 @@ class LeaveGroupDialog extends React.Component {
}
const LeaveGroupDialogPropTypes = {
toggleLeaveGroupDialog: PropTypes.func.isRequired,
groupID: PropTypes.string,
onGroupChanged: PropTypes.func.isRequired,
groupID: PropTypes.number.isRequired,
onLeavingGroup: PropTypes.func.isRequired,
toggleDialog: PropTypes.func.isRequired
};
LeaveGroupDialog.propTypes = LeaveGroupDialogPropTypes;

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Modal, ModalBody, Form } from 'reactstrap';
import { gettext, siteRoot, mediaUrl } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import SeahubModalCloseIcon from '../../components/common/seahub-modal-close';
import '../../css/lib-decrypt.css';
@ -56,7 +57,7 @@ class LibDecryptDialog extends React.Component {
return (
<Modal isOpen={true} toggle={this.toggle}>
<ModalBody>
<button type="button" className="close" onClick={this.toggle}><span aria-hidden="true">×</span></button>
<SeahubModalCloseIcon className="position-absolute top-0 end-0 m-0" toggle={this.toggle} />
<Form className="lib-decrypt-form text-center">
<img src={`${mediaUrl}img/lock.png`} alt="" aria-hidden="true" />
<p className="intro">{gettext('This library is password protected')}</p>

View File

@ -2,6 +2,7 @@ import React, { Fragment, useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
import { gettext, enableRepoAutoDel } from '../../utils/constants';
import { TAB } from '../../constants/repo-setting-tabs';
import LibHistorySettingPanel from './lib-settings/lib-history-setting-panel';
import LibAutoDelSettingPanel from './lib-settings/lib-old-files-auto-del-setting-panel';
import {
@ -16,14 +17,7 @@ import { useMetadataStatus } from '../../hooks';
import '../../css/lib-settings.css';
const TAB = {
HISTORY_SETTING: 'history_setting',
AUTO_DEL_SETTING: 'auto_delete_setting',
EXTENDED_PROPERTIES_SETTING: 'extended_properties_setting',
FACE_RECOGNITION_SETTING: 'face_recognition_setting',
TAGS_SETTING: 'tags_setting',
OCR_SETTING: 'ocr_setting',
};
const { enableSeafileAI, enableSeafileOCR } = window.app.config;
const propTypes = {
toggleDialog: PropTypes.func.isRequired,
@ -31,7 +25,7 @@ const propTypes = {
currentRepoInfo: PropTypes.object.isRequired
};
const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab }) => {
const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab, showMigrateTip }) => {
const [activeTab, setActiveTab] = useState(tab || TAB.HISTORY_SETTING);
const toggleTab = useCallback((tab) => {
@ -46,12 +40,11 @@ const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab }) => {
const { encrypted, is_admin } = currentRepoInfo;
const { enableMetadataManagement } = window.app.pageOptions;
const { enableFaceRecognition, updateEnableFaceRecognition } = useMetadata();
const { enableMetadata, updateEnableMetadata, enableTags, tagsLang, updateEnableTags, enableOCR, updateEnableOCR } = useMetadataStatus();
const { updateEnableFaceRecognition } = useMetadata();
const { enableMetadata, updateEnableMetadata, enableTags, tagsLang, updateEnableTags, enableOCR, updateEnableOCR, enableFaceRecognition } = useMetadataStatus();
const enableHistorySetting = is_admin; // repo owner, admin of the department which the repo belongs to, and ...
const enableAutoDelSetting = is_admin && enableRepoAutoDel;
const enableExtendedPropertiesSetting = !encrypted && is_admin && enableMetadataManagement;
const enableMetadataOtherSettings = enableExtendedPropertiesSetting && enableMetadata;
return (
<div>
@ -63,48 +56,102 @@ const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab }) => {
<Fragment>
<div className="lib-setting-nav p-4">
<Nav pills className="flex-column">
{enableHistorySetting && (
<NavItem role="tab" aria-selected={activeTab === TAB.HISTORY_SETTING} aria-controls="history-setting-panel">
<NavLink className={activeTab === TAB.HISTORY_SETTING ? 'active' : ''} onClick={(toggleTab.bind(this, TAB.HISTORY_SETTING))} tabIndex="0" onKeyDown={onTabKeyDown}>
{enableHistorySetting &&
<NavItem
role="tab"
aria-selected={activeTab === TAB.HISTORY_SETTING}
aria-controls="history-setting-panel"
>
<NavLink
className={activeTab === TAB.HISTORY_SETTING ? 'active' : ''}
onClick={toggleTab.bind(this, TAB.HISTORY_SETTING)}
tabIndex="0"
onKeyDown={onTabKeyDown}
>
{gettext('History')}
</NavLink>
</NavItem>
)}
{enableAutoDelSetting && (
<NavItem role="tab" aria-selected={activeTab === TAB.AUTO_DEL_SETTING} aria-controls="auto-del-setting-panel">
<NavLink className={activeTab === TAB.AUTO_DEL_SETTING ? 'active' : ''} onClick={toggleTab.bind(this, TAB.AUTO_DEL_SETTING)} tabIndex="0" onKeyDown={onTabKeyDown}>
}
{enableAutoDelSetting &&
<NavItem
role="tab"
aria-selected={activeTab === TAB.AUTO_DEL_SETTING}
aria-controls="auto-del-setting-panel"
>
<NavLink
className={activeTab === TAB.AUTO_DEL_SETTING ? 'active' : ''}
onClick={toggleTab.bind(this, TAB.AUTO_DEL_SETTING)}
tabIndex="0"
onKeyDown={onTabKeyDown}
>
{gettext('Auto deletion')}
</NavLink>
</NavItem>
)}
{enableExtendedPropertiesSetting && (
<NavItem role="tab" aria-selected={activeTab === TAB.EXTENDED_PROPERTIES_SETTING} aria-controls="extended-properties-setting-panel">
<NavLink className={activeTab === TAB.EXTENDED_PROPERTIES_SETTING ? 'active' : ''} onClick={toggleTab.bind(this, TAB.EXTENDED_PROPERTIES_SETTING)} tabIndex="0" onKeyDown={onTabKeyDown}>
{gettext('Extended properties')}
</NavLink>
</NavItem>
)}
{enableMetadataOtherSettings && (
<NavItem role="tab" aria-selected={activeTab === TAB.FACE_RECOGNITION_SETTING} aria-controls="face-recognition-setting-panel">
<NavLink className={activeTab === TAB.FACE_RECOGNITION_SETTING ? 'active' : ''} onClick={toggleTab.bind(this, TAB.FACE_RECOGNITION_SETTING)} tabIndex="0" onKeyDown={onTabKeyDown}>
{gettext('Face recognition')}
</NavLink>
</NavItem>
)}
{enableMetadataOtherSettings && (
<NavItem role="tab" aria-selected={activeTab === TAB.TAGS_SETTING} aria-controls="tags-setting-panel">
<NavLink className={activeTab === TAB.TAGS_SETTING ? 'active' : ''} onClick={toggleTab.bind(this, TAB.TAGS_SETTING)} tabIndex="0" onKeyDown={onTabKeyDown}>
{gettext('Tags')}
</NavLink>
</NavItem>
)}
{enableMetadataOtherSettings && (
<NavItem role="tab" aria-selected={activeTab === TAB.OCR_SETTING} aria-controls="ocr-setting-panel">
<NavLink className={activeTab === TAB.OCR_SETTING ? 'active' : ''} onClick={toggleTab.bind(this, TAB.OCR_SETTING)} tabIndex="0" onKeyDown={onTabKeyDown}>
{gettext('OCR')}
</NavLink>
</NavItem>
)}
}
{enableExtendedPropertiesSetting &&
<>
<NavItem
role="tab"
aria-selected={activeTab === TAB.EXTENDED_PROPERTIES_SETTING}
aria-controls="extended-properties-setting-panel"
>
<NavLink
className={activeTab === TAB.EXTENDED_PROPERTIES_SETTING ? 'active' : ''}
onClick={toggleTab.bind(this, TAB.EXTENDED_PROPERTIES_SETTING)}
tabIndex="0"
onKeyDown={onTabKeyDown}
>
{gettext('Extended properties')}
</NavLink>
</NavItem>
{enableSeafileAI &&
<NavItem
role="tab"
aria-selected={activeTab === TAB.FACE_RECOGNITION_SETTING}
aria-controls="face-recognition-setting-panel"
>
<NavLink
className={activeTab === TAB.FACE_RECOGNITION_SETTING ? 'active' : ''}
onClick={toggleTab.bind(this, TAB.FACE_RECOGNITION_SETTING)}
tabIndex="0"
onKeyDown={onTabKeyDown}
>
{gettext('Face recognition')}
</NavLink>
</NavItem>
}
<NavItem
role="tab"
aria-selected={activeTab === TAB.TAGS_SETTING}
aria-controls="tags-setting-panel"
>
<NavLink
className={activeTab === TAB.TAGS_SETTING ? 'active' : ''}
onClick={toggleTab.bind(this, TAB.TAGS_SETTING)}
tabIndex="0"
onKeyDown={onTabKeyDown}
>
{gettext('Tags')}
</NavLink>
</NavItem>
{enableSeafileAI && enableSeafileOCR &&
<NavItem
role="tab"
aria-selected={activeTab === TAB.OCR_SETTING}
aria-controls="ocr-setting-panel"
>
<NavLink
className={activeTab === TAB.OCR_SETTING ? 'active' : ''}
onClick={toggleTab.bind(this, TAB.OCR_SETTING)}
tabIndex="0"
onKeyDown={onTabKeyDown}
>
{gettext('OCR')}
</NavLink>
</NavItem>
}
</>
}
</Nav>
</div>
<TabContent activeTab={activeTab} className="flex-fill">
@ -134,17 +181,18 @@ const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab }) => {
/>
</TabPane>
)}
{(enableMetadataOtherSettings && activeTab === TAB.FACE_RECOGNITION_SETTING) && (
{(enableExtendedPropertiesSetting && activeTab === TAB.FACE_RECOGNITION_SETTING) && (
<TabPane tabId={TAB.FACE_RECOGNITION_SETTING} role="tabpanel" id="face-recognition-setting-panel">
<LibFaceRecognitionSettingPanel
repoID={repoID}
value={enableFaceRecognition}
submit={updateEnableFaceRecognition}
toggleDialog={toggleDialog}
enableMetadata={enableMetadata}
/>
</TabPane>
)}
{(enableMetadataOtherSettings && activeTab === TAB.TAGS_SETTING) && (
{(enableExtendedPropertiesSetting && activeTab === TAB.TAGS_SETTING) && (
<TabPane tabId={TAB.TAGS_SETTING} role="tabpanel" id="tags-setting-panel">
<LibMetadataTagsStatusSettingPanel
repoID={repoID}
@ -152,10 +200,12 @@ const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab }) => {
lang={tagsLang}
submit={updateEnableTags}
toggleDialog={toggleDialog}
enableMetadata={enableMetadata}
showMigrateTip={showMigrateTip}
/>
</TabPane>
)}
{(enableMetadataOtherSettings && activeTab === TAB.OCR_SETTING) && (
{(enableExtendedPropertiesSetting && activeTab === TAB.OCR_SETTING) && (
<TabPane tabId={TAB.OCR_SETTING} role="tabpanel" id="ocr-setting-panel">
<LibMetadataOCRStatusSettingPanel
repoID={repoID}
@ -163,6 +213,7 @@ const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab }) => {
lang={tagsLang}
submit={updateEnableOCR}
toggleDialog={toggleDialog}
enableMetadata={enableMetadata}
/>
</TabPane>
)}
@ -177,5 +228,3 @@ const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab }) => {
LibSettingsDialog.propTypes = propTypes;
export default LibSettingsDialog;
export { TAB };

View File

@ -1,6 +1,6 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Button, Input, InputGroup, InputGroupAddon } from 'reactstrap';
import { Button, Input, InputGroup } from 'reactstrap';
import { gettext, isPro, siteRoot } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
@ -279,18 +279,21 @@ class LibSubFolderSetGroupPermissionDialog extends React.Component {
if (this.state.showFileChooser) {
return (
<div>
<>
<div className="d-flex align-items-center justify-content-between pb-2 border-bottom">
<h6 className="font-weight-normal m-0">
<button className="sf3-font sf3-font-arrow rotate-180 d-inline-block back-icon border-0 bg-transparent text-secondary p-0 mr-2" onClick={this.toggleFileChooser} title={gettext('Back')} aria-label={gettext('Back')}></button>
{gettext('Add Folder')}
</h6>
<Button color="primary" size="sm" outline={true} onClick={this.handleSubmit}>{gettext('Submit')}</Button>
</div>
<FileChooser
repoID={this.props.repoID}
mode={'only_current_library'}
onDirentItemClick={this.toggleSubFolder}
onRepoItemClick={this.onRepoItemClick}
/>
<div className="modal-footer">
<Button color="secondary" onClick={this.toggleFileChooser}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
</div>
</div>
</>
);
}
@ -328,7 +331,7 @@ class LibSubFolderSetGroupPermissionDialog extends React.Component {
<td>
<InputGroup>
<Input value={this.state.folderPath} onChange={this.onSetSubFolder} />
<InputGroupAddon addonType="append"><Button className="sf2-icon-plus" onClick={this.toggleFileChooser}></Button></InputGroupAddon>
<Button className="sf2-icon-plus" onClick={this.toggleFileChooser}></Button>
</InputGroup>
</td>
}

View File

@ -1,7 +1,7 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { gettext, isPro, siteRoot } from '../../utils/constants';
import { Button, Input, InputGroup, InputGroupAddon } from 'reactstrap';
import { Button, Input, InputGroup } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import UserSelect from '../user-select';
@ -16,6 +16,7 @@ class UserItem extends React.Component {
this.state = {
isOperationShow: false
};
this.userSelect = React.createRef();
}
onMouseEnter = () => {
@ -111,6 +112,8 @@ class LibSubFolderSetUserPermissionDialog extends React.Component {
} else {
this.permissions = ['r', 'rw', 'cloud-edit', 'preview', 'invisible'];
}
this.userSelect = React.createRef();
}
handleUserSelectChange = (option) => {
@ -162,7 +165,7 @@ class LibSubFolderSetUserPermissionDialog extends React.Component {
permission: 'rw',
folderPath: '',
});
this.refs.userSelect.clearSelect();
this.userSelect.current.clearSelect();
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
@ -241,18 +244,21 @@ class LibSubFolderSetUserPermissionDialog extends React.Component {
if (this.state.showFileChooser) {
return (
<div>
<>
<div className="d-flex align-items-center justify-content-between pb-2 border-bottom">
<h6 className="font-weight-normal m-0">
<button className="sf3-font sf3-font-arrow rotate-180 d-inline-block back-icon border-0 bg-transparent text-secondary p-0 mr-2" onClick={this.toggleFileChooser} title={gettext('Back')} aria-label={gettext('Back')}></button>
{gettext('Add Folder')}
</h6>
<Button color="primary" size="sm" outline={true} onClick={this.handleFileChooserSubmit}>{gettext('Submit')}</Button>
</div>
<FileChooser
repoID={this.props.repoID}
mode={'only_current_library'}
onDirentItemClick={this.toggleSubFolder}
onRepoItemClick={this.onRepoItemClick}
/>
<div className="modal-footer">
<Button color="secondary" onClick={this.toggleFileChooser}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleFileChooserSubmit}>{gettext('Submit')}</Button>
</div>
</div>
</>
);
}
@ -277,7 +283,7 @@ class LibSubFolderSetUserPermissionDialog extends React.Component {
<tr>
<td>
<UserSelect
ref="userSelect"
ref={this.userSelect}
isMulti={true}
placeholder={gettext('Search users')}
onSelectChange={this.handleUserSelectChange}
@ -288,7 +294,7 @@ class LibSubFolderSetUserPermissionDialog extends React.Component {
<td>
<InputGroup>
<Input value={this.state.folderPath} onChange={this.onSetSubFolder} />
<InputGroupAddon addonType="append"><Button className="sf2-icon-plus" onClick={this.toggleFileChooser}></Button></InputGroupAddon>
<Button className="sf2-icon-plus" onClick={this.toggleFileChooser}></Button>
</InputGroup>
</td>
}

View File

@ -8,7 +8,7 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header';
import '../../css/manage-members-dialog.css';
const propTypes = {
groupID: PropTypes.string,
groupID: PropTypes.number.isRequired,
isOwner: PropTypes.bool.isRequired,
toggleManageMembersDialog: PropTypes.func,
toggleDepartmentDetailDialog: PropTypes.func,

View File

@ -22,6 +22,7 @@ class AddOrgAdminDialog extends React.Component {
errMessage: '',
};
this.options = [];
this.userSelect = React.createRef();
}
handleSelectChange = (option) => {
@ -54,7 +55,7 @@ class AddOrgAdminDialog extends React.Component {
<SeahubModalHeader toggle={this.toggle}>{gettext('Add Admins')}</SeahubModalHeader>
<ModalBody>
<UserSelect
ref="userSelect"
ref={this.userSelect}
isMulti={false}
placeholder={gettext('Select a user as admin')}
onSelectChange={this.handleSelectChange}

View File

@ -1,8 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, Input, ModalBody, ModalFooter, Label, Form, InputGroup, InputGroupAddon, FormGroup } from 'reactstrap';
import { Button, Modal, Input, ModalBody, ModalFooter, Label, Form, InputGroup, FormGroup } from 'reactstrap';
import { gettext } from '../../utils/constants';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
import { Utils } from '../../utils/utils';
const propTypes = {
toggle: PropTypes.func.isRequired,
@ -48,7 +49,7 @@ class AddOrgUserDialog extends React.Component {
};
generatePassword = () => {
let val = Math.random().toString(36).substr(5);
let val = Utils.generatePassword(8);
this.setState({
password: val,
newPassword: val,
@ -146,14 +147,12 @@ class AddOrgUserDialog extends React.Component {
<Label for="userPwd">{gettext('Password')}</Label>
<InputGroup className="passwd">
<Input id="userPwd" innerRef={input => {this.passwdInput = input;}} value={this.state.password || ''} onChange={this.inputPassword} />
<InputGroupAddon addonType="append">
<Button onClick={this.togglePasswordVisible}>
<i className={`link-operation-icon sf3-font sf3-font-eye${this.state.isPasswordVisible ? '-slash' : ''}`}></i>
</Button>
<Button onClick={this.generatePassword}>
<i className="link-operation-icon sf3-font sf3-font-magic"></i>
</Button>
</InputGroupAddon>
<Button onClick={this.togglePasswordVisible}>
<i className={`link-operation-icon sf3-font sf3-font-eye${this.state.isPasswordVisible ? '-slash' : ''}`}></i>
</Button>
<Button onClick={this.generatePassword}>
<i className="link-operation-icon sf3-font sf3-font-magic"></i>
</Button>
</InputGroup>
</FormGroup>
<FormGroup>

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalBody, ModalFooter, Input, InputGroupAddon, InputGroup } from 'reactstrap';
import { Button, Modal, ModalBody, ModalFooter, Input, InputGroup, InputGroupText } from 'reactstrap';
import { gettext, orgID } from '../../utils/constants';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
import { orgAdminAPI } from '../../utils/org-admin-api';
@ -9,7 +9,7 @@ import toaster from '../toast';
const propTypes = {
toggle: PropTypes.func.isRequired,
groupID: PropTypes.number.isRequired,
group: PropTypes.object.isRequired,
onSetQuota: PropTypes.func.isRequired,
};
@ -29,7 +29,7 @@ class SetGroupQuotaDialog extends React.Component {
if ((quota.length && myReg.test(quota)) || quota == -2) {
this.setState({ errMessage: '' });
let newQuota = this.state.quota == -2 ? this.state.quota : this.state.quota * 1000000;
orgAdminAPI.orgAdminSetGroupQuota(orgID, this.props.groupID, newQuota).then((res) => {
orgAdminAPI.orgAdminSetGroupQuota(orgID, this.props.group.id, newQuota).then((res) => {
this.props.toggle();
this.props.onSetQuota(res.data);
}).catch(error => {
@ -55,10 +55,15 @@ class SetGroupQuotaDialog extends React.Component {
};
render() {
const group = this.props.group;
const oldQuota = Utils.bytesToSize(group.quota);
const message = gettext('The current quota for {group_name} is {quota}').replace('{group_name}', group.name).replace('{quota}', oldQuota);
return (
<Modal isOpen={true} toggle={this.props.toggle} autoFocus={false}>
<SeahubModalHeader toggle={this.props.toggle}>{gettext('Set Quota')}</SeahubModalHeader>
<ModalBody>
<p>{message}</p>
<p>{gettext('Please enter a new quota')}</p>
<InputGroup>
<Input
onKeyDown={this.handleKeyDown}
@ -66,7 +71,7 @@ class SetGroupQuotaDialog extends React.Component {
onChange={this.handleChange}
autoFocus={true}
/>
<InputGroupAddon addonType="append">{'MB'}</InputGroupAddon>
<InputGroupText addonType="append">{'MB'}</InputGroupText>
</InputGroup>
<p className="tip">
<br/><span>{gettext('An integer that is greater than 0 or equal to -2.')}</span><br/>

View File

@ -55,7 +55,6 @@ class PublishWikiDialog extends React.Component {
});
} else {
this.props.onPublish(DEFAULT_URL + this.state.url.trim());
this.toggle();
}
};

View File

@ -66,6 +66,7 @@ class Rename extends React.Component {
handleKeyDown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.handleSubmit();
}
};

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import { Modal, ModalBody, ModalFooter, Input, Button } from 'reactstrap';
import { Modal, ModalBody, ModalFooter, Input, Label, Button } from 'reactstrap';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
import toaster from '../toast';
@ -12,7 +12,7 @@ class RenameGroupDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
newGroupName: this.props.currentGroupName,
newGroupName: this.props.groupName,
isSubmitBtnActive: false,
};
}
@ -30,48 +30,42 @@ class RenameGroupDialog extends React.Component {
});
};
renameGroup = () => {
let name = this.state.newGroupName.trim();
if (name) {
let that = this;
seafileAPI.renameGroup(this.props.groupID, name).then((res) => {
that.props.loadGroup(this.props.groupID);
that.props.onGroupChanged(res.data.id);
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
this.setState({
newGroupName: '',
handleSubmit = () => {
const { groupID } = this.props;
const { newGroupName } = this.state;
seafileAPI.renameGroup(groupID, newGroupName.trim()).then((res) => {
const { name } = res.data;
this.props.onGroupNameChanged(name);
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
this.props.toggleRenameGroupDialog();
this.props.toggleDialog();
};
handleKeyDown = (event) => {
if (event.keyCode === 13) {
this.renameGroup();
this.handleSubmit();
}
};
render() {
return (
<Modal isOpen={this.props.showRenameGroupDialog} toggle={this.props.toggleRenameGroupDialog}>
<SeahubModalHeader>{gettext('Rename Group')}</SeahubModalHeader>
<Modal isOpen={true} toggle={this.props.toggleDialog}>
<SeahubModalHeader toggle={this.props.toggleDialog}>{gettext('Rename Group')}</SeahubModalHeader>
<ModalBody>
<label htmlFor="newGroupName">{gettext('Rename group to')}</label>
<Label for="group-name">{gettext('Rename group to')}</Label>
<Input
type="text"
id="newGroupName"
name="new-group-name"
id="group-name"
value={this.state.newGroupName}
onChange={this.handleGroupNameChange}
onKeyDown={this.handleKeyDown}
/>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggleRenameGroupDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.renameGroup} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
<Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleSubmit} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
@ -79,12 +73,10 @@ class RenameGroupDialog extends React.Component {
}
const RenameGroupDialogPropTypes = {
showRenameGroupDialog: PropTypes.bool.isRequired,
toggleRenameGroupDialog: PropTypes.func.isRequired,
loadGroup: PropTypes.func.isRequired,
groupID: PropTypes.string,
onGroupChanged: PropTypes.func.isRequired,
currentGroupName: PropTypes.string.isRequired,
toggleDialog: PropTypes.func.isRequired,
groupID: PropTypes.number,
onGroupNameChanged: PropTypes.func.isRequired,
groupName: PropTypes.string.isRequired,
};
RenameGroupDialog.propTypes = RenameGroupDialogPropTypes;

View File

@ -5,7 +5,7 @@ import { Modal, ModalBody } from 'reactstrap';
import { Utils } from '../../utils/utils';
import { gettext, siteRoot, enableRepoSnapshotLabel as showLabel } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import Loading from '../../components/loading';
import Loading from '../loading';
import Paginator from '../../components/paginator';
import ModalPortal from '../../components/modal-portal';
import CommitDetails from '../../components/dialog/commit-details';

View File

@ -4,7 +4,7 @@ import { Link } from '@gatsbyjs/reach-router';
import { Utils } from '../../../utils/utils';
import { seafileAPI } from '../../../utils/seafile-api';
import { gettext, siteRoot, isPro, username } from '../../../utils/constants';
import Loading from '../../../components/loading';
import Loading from '../../loading';
import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip';
import SharePermissionEditor from '../../../components/select-editor/share-permission-editor';

View File

@ -7,7 +7,7 @@ import { Utils } from '../../../utils/utils';
import { seafileAPI } from '../../../utils/seafile-api';
import { repoShareAdminAPI } from '../../../utils/repo-share-admin-api';
import { gettext, siteRoot } from '../../../utils/constants';
import Loading from '../../../components/loading';
import Loading from '../../loading';
import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip';
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';

View File

@ -7,7 +7,7 @@ import { Utils } from '../../../utils/utils';
import { seafileAPI } from '../../../utils/seafile-api';
import { repoShareAdminAPI } from '../../../utils/repo-share-admin-api';
import { gettext, siteRoot } from '../../../utils/constants';
import Loading from '../../../components/loading';
import Loading from '../../loading';
import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip';
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';

View File

@ -4,7 +4,7 @@ import { Link } from '@gatsbyjs/reach-router';
import { Utils } from '../../../utils/utils';
import { seafileAPI } from '../../../utils/seafile-api';
import { gettext, siteRoot, isPro, username } from '../../../utils/constants';
import Loading from '../../../components/loading';
import Loading from '../../loading';
import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip';
import SharePermissionEditor from '../../../components/select-editor/share-permission-editor';

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalBody, ModalFooter, Alert, Button, Input, InputGroup, InputGroupAddon } from 'reactstrap';
import { Modal, ModalBody, ModalFooter, Alert, Button, Input, InputGroup } from 'reactstrap';
import { gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
@ -74,14 +74,12 @@ class ResetWebdavPassword extends Component {
<ModalBody>
<InputGroup>
<Input type={this.state.isPasswordVisible ? 'text' : 'password'} value={this.state.password} onChange={this.handleInputChange} autoComplete="new-password"/>
<InputGroupAddon addonType="append">
<Button onClick={this.togglePasswordVisible}>
<i className={`sf3-font sf3-font-eye${this.state.isPasswordVisible ? '' : '-slash'}`}></i>
</Button>
<Button onClick={this.generatePassword}>
<i className="sf3-font sf3-font-magic"></i>
</Button>
</InputGroupAddon>
<Button onClick={this.togglePasswordVisible}>
<i className={`sf3-font sf3-font-eye${this.state.isPasswordVisible ? '' : '-slash'}`}></i>
</Button>
<Button onClick={this.generatePassword}>
<i className="sf3-font sf3-font-magic"></i>
</Button>
</InputGroup>
<p className="form-text text-muted m-0">{passwordTip}</p>
{this.state.errMsg && <Alert color="danger" className="m-0 mt-2">{gettext(this.state.errMsg)}</Alert>}

View File

@ -6,7 +6,6 @@ import { MODE_TYPE_MAP } from '../../constants';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext } from '../../utils/constants';
import { RepoInfo } from '../../models';
import { ModalPortal } from '@seafile/sf-metadata-ui-component';
import CreateFolder from '../dialog/create-folder-dialog';
const LibraryOption = ({ mode, label, currentMode, onUpdateMode }) => {
@ -123,7 +122,7 @@ class SelectDirentBody extends React.Component {
};
render() {
const { mode, repoList, currentRepo, selectedRepo, currentPath, selectedPath, isSupportOtherLibraries, errMessage, searchStatus, searchResults, selectedSearchedRepo, selectedSearchedItem } = this.props;
const { mode, repoList, currentRepo, selectedRepo, currentPath, selectedPath, isSupportOtherLibraries = true, errMessage, searchStatus, searchResults, selectedSearchedRepo, selectedSearchedItem } = this.props;
let repoListWrapperKey = 'repo-list-wrapper';
if (selectedSearchedItem && selectedSearchedItem.repoID) {
repoListWrapperKey = `${repoListWrapperKey}-${selectedSearchedItem.repoID}`;
@ -131,7 +130,7 @@ class SelectDirentBody extends React.Component {
return (
<Row>
<Col className='repo-list-col border-right'>
<Col className='repo-list-col border-end'>
<LibraryOption
mode={MODE_TYPE_MAP.ONLY_CURRENT_LIBRARY}
label={gettext('Current Library')}
@ -193,14 +192,12 @@ class SelectDirentBody extends React.Component {
</ModalFooter>
</Col>
{this.state.showCreateFolderDialog && (
<ModalPortal>
<CreateFolder
parentPath={this.props.selectedPath}
onAddFolder={this.createFolder}
checkDuplicatedName={this.checkDuplicatedName}
addFolderCancel={this.onToggleCreateFolder}
/>
</ModalPortal>
<CreateFolder
parentPath={this.props.selectedPath}
onAddFolder={this.createFolder}
checkDuplicatedName={this.checkDuplicatedName}
addFolderCancel={this.onToggleCreateFolder}
/>
)}
</Row>
);
@ -233,8 +230,4 @@ SelectDirentBody.propTypes = {
selectedSearchedItem: PropTypes.object,
};
SelectDirentBody.defaultProps = {
isSupportOtherLibraries: true,
};
export default SelectDirentBody;

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalBody, ModalFooter, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
import { Modal, ModalBody, ModalFooter, InputGroup, InputGroupText } from 'reactstrap';
import { gettext } from '../../utils/constants';
import { orgAdminAPI } from '../../utils/org-admin-api';
import { Utils } from '../../utils/utils';
@ -66,9 +66,7 @@ class SetOrgUserDefaultQuota extends React.Component {
<React.Fragment>
<InputGroup>
<input type="text" className="form-control" value={inputValue} onChange={this.handleInputChange} />
<InputGroupAddon addonType="append">
<InputGroupText>MB</InputGroupText>
</InputGroupAddon>
<InputGroupText>MB</InputGroupText>
</InputGroup>
<p className="small text-secondary mt-2 mb-2">{gettext('Tip: 0 means default limit')}</p>
{formErrorMsg && <p className="error m-0 mt-2">{formErrorMsg}</p>}

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalBody, ModalFooter, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
import { Modal, ModalBody, ModalFooter, InputGroup, InputGroupText } from 'reactstrap';
import { gettext } from '../../utils/constants';
import { orgAdminAPI } from '../../utils/org-admin-api';
import { Utils } from '../../utils/utils';
@ -68,9 +68,7 @@ class SetOrgUserQuota extends React.Component {
<React.Fragment>
<InputGroup>
<input type="text" className="form-control" value={inputValue} onChange={this.handleInputChange} />
<InputGroupAddon addonType="append">
<InputGroupText>MB</InputGroupText>
</InputGroupAddon>
<InputGroupText>MB</InputGroupText>
</InputGroup>
<p className="small text-secondary mt-2 mb-2">{gettext('Tip: 0 means default limit')}</p>
{formErrorMsg && <p className="error m-0 mt-2">{formErrorMsg}</p>}

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalBody, ModalFooter, Alert, Button, Input, InputGroup, InputGroupAddon } from 'reactstrap';
import { Modal, ModalBody, ModalFooter, Alert, Button, Input, InputGroup } from 'reactstrap';
import { gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
@ -74,14 +74,12 @@ class SetWebdavPassword extends Component {
<ModalBody>
<InputGroup>
<Input type={this.state.isPasswordVisible ? 'text' : 'password'} value={this.state.password} onChange={this.handleInputChange} autoComplete="new-password"/>
<InputGroupAddon addonType="append">
<Button onClick={this.togglePasswordVisible}>
<i className={`sf3-font sf3-font-eye${this.state.isPasswordVisible ? '' : 'slash'}`}></i>
</Button>
<Button onClick={this.generatePassword}>
<i className="sf3-font sf3-font-magic"></i>
</Button>
</InputGroupAddon>
<Button onClick={this.togglePasswordVisible}>
<i className={`sf3-font sf3-font-eye${this.state.isPasswordVisible ? '' : '-slash'}`}></i>
</Button>
<Button onClick={this.generatePassword}>
<i className="sf3-font sf3-font-magic"></i>
</Button>
</InputGroup>
<p className="form-text text-muted m-0">{passwordTip}</p>
{this.state.errMsg && <Alert color="danger" className="m-0 mt-2">{gettext(this.state.errMsg)}</Alert>}

View File

@ -326,7 +326,9 @@ class ShareDialog extends React.Component {
<div>
<Modal isOpen={true} style={{ maxWidth: '760px' }} className="share-dialog" toggle={this.props.toggleDialog}>
<SeahubModalHeader toggle={this.props.toggleDialog} tag="div">
<h5 className="text-truncate">{gettext('Share')} <span className="op-target" title={itemName}>{itemName}</span></h5>
<h5 className="text-truncate m-0">
{gettext('Share')} <span className="op-target" title={itemName}>{itemName}</span>
</h5>
{this.renderExternalShareMessage()}
</SeahubModalHeader>
<ModalBody className="share-dialog-content" role="tablist">

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { Button } from 'reactstrap';
import { gettext, isPro, enableShareToDepartment } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import { Utils, isMobile } from '../../utils/utils';
import toaster from '../toast';
import SharePermissionEditor from '../select-editor/share-permission-editor';
import EventBus from '../common/event-bus';
@ -40,6 +40,36 @@ class GroupItem extends React.Component {
render() {
let item = this.props.item;
let currentPermission = Utils.getSharedPermission(item);
if (isMobile) {
return (
<tr>
<td className='name'>{item.group_info.name}</td>
<td>
<SharePermissionEditor
repoID={this.props.repoID}
isTextMode={true}
autoFocus={true}
isEditIconShow={this.state.isOperationShow}
currentPermission={currentPermission}
permissions={this.props.permissions}
onPermissionChanged={this.onChangeUserPermission}
/>
</td>
<td>
<span
tabIndex="0"
role="button"
className='sf2-icon-x3 action-icon'
onClick={this.deleteShareItem}
onKeyDown={Utils.onKeyDown}
title={gettext('Delete')}
aria-label={gettext('Delete')}
>
</span>
</td>
</tr>
);
}
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} tabIndex="0" onFocus={this.onMouseEnter}>
<td className='name'>{item.group_info.name}</td>
@ -352,7 +382,7 @@ class ShareToGroup extends React.Component {
};
render() {
const thead = (
let thead = (
<thead>
<tr>
<th width="47%">{gettext('Group')}</th>
@ -361,9 +391,20 @@ class ShareToGroup extends React.Component {
</tr>
</thead>
);
if (isMobile) {
thead = (
<thead>
<tr>
<th width="43%">{gettext('Group')}</th>
<th width="35%">{gettext('Permission')}</th>
<th width="22%"></th>
</tr>
</thead>
);
}
return (
<Fragment>
<table className="w-xs-200">
<table>
{thead}
<tbody>
<tr>
@ -392,7 +433,7 @@ class ShareToGroup extends React.Component {
/>
</td>
<td>
<Button color="primary" onClick={this.shareToGroup}>{gettext('Submit')}</Button>
<Button color="primary" onClick={this.shareToGroup} size={isMobile ? 'sm' : 'md'}>{gettext('Submit')}</Button>
</td>
</tr>
{this.state.errorMsg.length > 0 &&
@ -408,7 +449,7 @@ class ShareToGroup extends React.Component {
</tbody>
</table>
<div className="share-list-container">
<table className="table-thead-hidden w-xs-200">
<table className="table-thead-hidden">
{thead}
<GroupList
repoID={this.props.repoID}

View File

@ -4,7 +4,7 @@ import classnames from 'classnames';
import { gettext, isPro, cloudMode, isOrgContext } from '../../utils/constants';
import { Button } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import { Utils, isMobile } from '../../utils/utils';
import toaster from '../toast';
import UserSelect from '../user-select';
import SharePermissionEditor from '../select-editor/share-permission-editor';
@ -21,6 +21,7 @@ class UserItem extends React.Component {
isOperationShow: false,
isUserDetailsPopoverOpen: false
};
this.userSelect = React.createRef();
}
onMouseEnter = () => {
@ -51,6 +52,65 @@ class UserItem extends React.Component {
let item = this.props.item;
let currentPermission = Utils.getSharedPermission(item);
const { isUserDetailsPopoverOpen } = this.state;
if (isMobile) {
return (
<tr>
<td className="name">
<div className="position-relative d-flex align-items-center">
<img
src={item.user_info.avatar_url}
width="24"
alt={item.user_info.nickname}
className="rounded-circle mr-2 cursor-pointer"
onMouseEnter={this.userAvatarOnMouseEnter}
onMouseLeave={this.userAvatarOnMouseLeave}
/>
<span>{item.user_info.nickname}</span>
{isUserDetailsPopoverOpen && (
<div className="user-details-popover p-4 position-absolute w-100 mt-1">
<div className="user-details-main pb-3">
<img
src={item.user_info.avatar_url}
width="40"
alt={item.user_info.nickname}
className="rounded-circle mr-2"
/>
<span className="user-details-name">{item.user_info.nickname}</span>
</div>
<dl className="m-0 mt-3 d-flex">
<dt className="m-0 mr-3">{gettext('Email')}</dt>
<dd className="m-0">{item.user_info.contact_email}</dd>
</dl>
</div>
)}
</div>
</td>
<td>
<SharePermissionEditor
repoID={this.props.repoID}
isTextMode={true}
autoFocus={true}
isEditIconShow={true}
currentPermission={currentPermission}
permissions={this.props.permissions}
onPermissionChanged={this.onChangeUserPermission}
/>
</td>
<td>
<span
tabIndex="0"
role="button"
className='sf2-icon-x3 action-icon'
onClick={this.deleteShareItem}
onKeyDown={Utils.onKeyDown}
title={gettext('Delete')}
aria-label={gettext('Delete')}
>
</span>
</td>
</tr>
);
}
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} tabIndex="0" onFocus={this.onMouseEnter}>
<td className="name">
@ -255,7 +315,7 @@ class ShareToUser extends React.Component {
selectedOption: null,
permission: 'rw',
});
this.refs.userSelect.clearSelect();
this.userSelect.current.clearSelect();
}).catch(error => {
if (error.response) {
let message = gettext('Library can not be shared to owner.');
@ -281,7 +341,7 @@ class ShareToUser extends React.Component {
selectedOption: null,
permission: 'rw',
});
this.refs.userSelect.clearSelect();
this.userSelect.current.clearSelect();
}).catch(error => {
if (error.response) {
let message = gettext('Library can not be shared to owner.');
@ -386,7 +446,7 @@ class ShareToUser extends React.Component {
selectedOption: null,
permission: 'rw',
});
this.refs.userSelect.clearSelect();
this.userSelect.current.clearSelect();
}).catch(error => {
if (error.response) {
let message = gettext('Library can not be shared to owner.');
@ -412,7 +472,7 @@ class ShareToUser extends React.Component {
selectedOption: null,
permission: 'rw',
});
this.refs.userSelect.clearSelect();
this.userSelect.current.clearSelect();
}).catch(error => {
if (error.response) {
let message = gettext('Library can not be shared to owner.');
@ -437,7 +497,7 @@ class ShareToUser extends React.Component {
showDeptBtn = false;
}
let { sharedItems } = this.state;
const thead = (
let thead = (
<thead>
<tr>
<th width="47%">{gettext('User')}</th>
@ -446,16 +506,27 @@ class ShareToUser extends React.Component {
</tr>
</thead>
);
if (isMobile) {
thead = (
<thead>
<tr>
<th width="43%">{gettext('User')}</th>
<th width="35%">{gettext('Permission')}</th>
<th width="22%"></th>
</tr>
</thead>
);
}
return (
<div className="share-link-container">
<table className="w-xs-200">
<table>
{thead}
<tbody>
<tr>
<td>
<div className='add-members'>
<UserSelect
ref="userSelect"
ref={this.userSelect}
isMulti={true}
className={classnames('reviewer-select', { 'user-select-right-btn': showDeptBtn })}
placeholder={gettext('Search users...')}
@ -484,7 +555,7 @@ class ShareToUser extends React.Component {
/>
</td>
<td>
<Button color="primary" onClick={this.shareToUser}>{gettext('Submit')}</Button>
<Button color="primary" onClick={this.shareToUser} size={isMobile ? 'sm' : 'md'}>{gettext('Submit')}</Button>
</td>
</tr>
{this.state.errorMsg.length > 0 &&
@ -505,7 +576,7 @@ class ShareToUser extends React.Component {
</tbody>
</table>
<div className="share-list-container">
<table className="table-thead-hidden w-xs-200">
<table className="table-thead-hidden">
{thead}
<UserList
repoID={this.props.repoID}

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalBody, ModalFooter, Button, Form, FormGroup, Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
import { Modal, ModalBody, ModalFooter, Button, Form, FormGroup, Input, InputGroup, InputGroupText } from 'reactstrap';
import { gettext } from '../../../utils/constants';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
@ -59,9 +59,7 @@ class SetQuotaDialog extends React.Component {
onKeyDown={this.handleKeyDown}
onChange={this.handleQuotaChange}
/>
<InputGroupAddon addonType="append">
<InputGroupText>MB</InputGroupText>
</InputGroupAddon>
<InputGroupText>MB</InputGroupText>
</InputGroup>
<p className="small text-secondary mt-2 mb-2">
{gettext('An integer that is greater than or equal to 0.')}

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalBody, ModalFooter, Button, Form, FormGroup, Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
import { Modal, ModalBody, ModalFooter, Button, Form, FormGroup, Input, InputGroup, InputGroupText } from 'reactstrap';
import { gettext } from '../../../utils/constants';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
@ -60,9 +60,7 @@ class SysAdminSetUploadDownloadRateLimitDialog extends React.Component {
onKeyDown={this.handleKeyDown}
onChange={this.handleRateLimitChange}
/>
<InputGroupAddon addonType="append">
<InputGroupText>kB/s</InputGroupText>
</InputGroupAddon>
<InputGroupText>kB/s</InputGroupText>
</InputGroup>
<p className="small text-secondary mt-2 mb-2">
{gettext('An integer that is greater than or equal to 0.')}

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Alert, Modal, ModalBody, ModalFooter, Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon } from 'reactstrap';
import { Alert, Modal, ModalBody, ModalFooter, Button, Form, FormGroup, Label, Input, InputGroup } from 'reactstrap';
import { gettext } from '../../../utils/constants';
import { Utils } from '../../../utils/utils';
import SysAdminUserRoleEditor from '../../../components/select-editor/sysadmin-user-role-editor';
@ -154,14 +154,12 @@ class SysAdminAddUserDialog extends React.Component {
<Label>{gettext('Password')}</Label>
<InputGroup>
<Input autoComplete="new-password" type={isPasswordVisible ? 'text' : 'password'} value={password || ''} onChange={this.inputPassword} />
<InputGroupAddon addonType="append">
<Button className="mt-0" onClick={this.togglePasswordVisible}>
<i className={`link-operation-icon sf3-font sf3-font-eye${this.state.isPasswordVisible ? '' : '-slash'}`}></i>
</Button>
<Button className="mt-0" onClick={this.generatePassword}>
<i className="link-operation-icon sf3-font sf3-font-magic"></i>
</Button>
</InputGroupAddon>
<Button className="mt-0" onClick={this.togglePasswordVisible}>
<i className={`link-operation-icon sf3-font sf3-font-eye${this.state.isPasswordVisible ? '' : '-slash'}`}></i>
</Button>
<Button className="mt-0" onClick={this.generatePassword}>
<i className="link-operation-icon sf3-font sf3-font-magic"></i>
</Button>
</InputGroup>
</FormGroup>
<FormGroup>

View File

@ -18,6 +18,7 @@ class SysAdminGroupAddMemberDialog extends React.Component {
selectedOptions: null,
isSubmitBtnDisabled: true
};
this.userSelect = React.createRef();
}
handleSelectChange = (options) => {
@ -40,7 +41,7 @@ class SysAdminGroupAddMemberDialog extends React.Component {
<SeahubModalHeader toggle={this.props.toggle}>{gettext('Add Member')}</SeahubModalHeader>
<ModalBody>
<UserSelect
ref="userSelect"
ref={this.userSelect}
isMulti={true}
placeholder={gettext('Search users')}
onSelectChange={this.handleSelectChange}

View File

@ -20,6 +20,7 @@ class SysAdminTransferGroupDialog extends React.Component {
selectedOptions: null,
submitBtnDisabled: true
};
this.userSelect = React.createRef();
}
handleSelectChange = (options) => {
@ -48,7 +49,7 @@ class SysAdminTransferGroupDialog extends React.Component {
</SeahubModalHeader>
<ModalBody>
<UserSelect
ref="userSelect"
ref={this.userSelect}
isMulti={false}
placeholder={gettext('Select a user')}
onSelectChange={this.handleSelectChange}

View File

@ -18,6 +18,7 @@ class SysAdminRepoTransferDialog extends React.Component {
selectedOption: null,
errorMsg: [],
};
this.userSelect = React.createRef();
}
handleSelectChange = (option) => {
@ -41,7 +42,7 @@ class SysAdminRepoTransferDialog extends React.Component {
</SeahubModalHeader>
<ModalBody>
<UserSelect
ref="userSelect"
ref={this.userSelect}
isMulti={false}
placeholder={gettext('Search users')}
onSelectChange={this.handleSelectChange}

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalBody, ModalFooter, Input, InputGroupAddon, InputGroup } from 'reactstrap';
import { Button, Modal, ModalBody, ModalFooter, Input, InputGroup, InputGroupText } from 'reactstrap';
import { gettext } from '../../../utils/constants';
import { systemAdminAPI } from '../../../utils/system-admin-api';
import { Utils } from '../../../utils/utils';
@ -9,7 +9,7 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header';
const propTypes = {
toggle: PropTypes.func.isRequired,
groupID: PropTypes.number.isRequired,
group: PropTypes.object.isRequired,
onSetQuota: PropTypes.func.isRequired,
};
@ -29,7 +29,7 @@ class SetGroupQuotaDialog extends React.Component {
if ((quota.length && numberReg.test(quota)) || quota == -2) {
this.setState({ errMessage: '' });
let newQuota = this.state.quota == -2 ? this.state.quota : this.state.quota * 1000000;
systemAdminAPI.sysAdminUpdateDepartmentQuota(this.props.groupID, newQuota).then((res) => {
systemAdminAPI.sysAdminUpdateDepartmentQuota(this.props.group.id, newQuota).then((res) => {
this.props.toggle();
this.props.onSetQuota(res.data);
}).catch(error => {
@ -55,10 +55,15 @@ class SetGroupQuotaDialog extends React.Component {
};
render() {
const group = this.props.group;
const oldQuota = Utils.bytesToSize(group.quota);
const message = gettext('The current quota for {group_name} is {quota}').replace('{group_name}', group.name).replace('{quota}', oldQuota);
return (
<Modal isOpen={true} toggle={this.props.toggle} autoFocus={false}>
<SeahubModalHeader toggle={this.props.toggle}>{gettext('Set Quota')}</SeahubModalHeader>
<ModalBody>
<p>{message}</p>
<p>{gettext('Please enter a new quota')}</p>
<InputGroup>
<Input
onKeyDown={this.handleKeyDown}
@ -66,7 +71,7 @@ class SetGroupQuotaDialog extends React.Component {
onChange={this.handleChange}
autoFocus={true}
/>
<InputGroupAddon addonType="append">{'MB'}</InputGroupAddon>
<InputGroupText>{'MB'}</InputGroupText>
</InputGroup>
<p className="tip">
<br/><span>{gettext('An integer that is greater than 0 or equal to -2.')}</span><br/>

View File

@ -15,6 +15,7 @@ class UserItem extends React.Component {
this.state = {
isOperationShow: false
};
this.userSelect = React.createRef();
}
onMouseEnter = () => {
@ -169,7 +170,7 @@ class SysAdminShareToUser extends React.Component {
selectedOption: null,
permission: 'rw',
});
this.refs.userSelect.clearSelect();
this.userSelect.current.clearSelect();
}).catch(error => {
if (error.response) {
let message = gettext('Library can not be shared to owner.');
@ -235,7 +236,7 @@ class SysAdminShareToUser extends React.Component {
<tr>
<td>
<UserSelect
ref="userSelect"
ref={this.userSelect}
isMulti={true}
placeholder={gettext('Search users')}
onSelectChange={this.handleSelectChange}

View File

@ -0,0 +1,80 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalBody, ModalFooter, Form, FormGroup, Input, Label } from 'reactstrap';
import { gettext } from '../../../utils/constants';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
const propTypes = {
toggleDialog: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired
};
class SysAdminUserDeactivateDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
keepSharing: true
};
}
handleOptionChange = (e) => {
this.setState({ keepSharing: e.target.value === 'true' });
};
submit = () => {
this.props.onSubmit(this.state.keepSharing);
this.props.toggleDialog();
};
render() {
return (
<Modal isOpen={true} toggle={this.props.toggleDialog}>
<SeahubModalHeader toggle={this.props.toggleDialog}>
{gettext('Set user inactive')}
</SeahubModalHeader>
<ModalBody>
<Form>
<FormGroup tag="fieldset">
<p>{gettext('Do you want to keep the sharing relationships?')}</p>
<FormGroup check>
<Label check>
<Input
type="radio"
name="keepSharing"
value="true"
checked={this.state.keepSharing === true}
onChange={this.handleOptionChange}
className="mr-2"
/>
{gettext('Keep sharing')}
</Label>
</FormGroup>
<FormGroup check>
<Label check>
<Input
type="radio"
name="keepSharing"
value="false"
checked={this.state.keepSharing === false}
onChange={this.handleOptionChange}
className="mr-2"
/>
{gettext('Do not keep sharing')}
</Label>
</FormGroup>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.submit}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
}
}
SysAdminUserDeactivateDialog.propTypes = propTypes;
export default SysAdminUserDeactivateDialog;

View File

@ -1,108 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Popover, PopoverBody } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import { TAG_COLORS } from '../../constants';
import toaster from '../toast';
import '../../css/repo-tag.css';
const tagColorPropTypes = {
tag: PropTypes.object.isRequired,
repoID: PropTypes.string.isRequired
};
class TagColor extends React.Component {
constructor(props) {
super(props);
this.state = {
tagColor: this.props.tag.color,
isPopoverOpen: false
};
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.tag.color !== this.props.tag.color) {
this.setState({
tagColor: nextProps.tag.color,
});
}
}
togglePopover = () => {
this.setState({
isPopoverOpen: !this.state.isPopoverOpen
});
};
selectTagColor = (e) => {
const newColor = e.target.value;
const { repoID, tag } = this.props;
const { id, name } = tag;
seafileAPI.updateRepoTag(repoID, id, name, newColor).then(() => {
this.setState({
tagColor: newColor,
isPopoverOpen: !this.state.isPopoverOpen
});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
render() {
const { isPopoverOpen, tagColor } = this.state;
const { tag } = this.props;
const { id, color } = tag;
let colorList = [...TAG_COLORS];
// for color from previous color options
if (colorList.indexOf(color) == -1) {
colorList.unshift(color);
}
return (
<div>
<span
id={`tag-${id}-color`}
className="tag-color cursor-pointer rounded-circle d-flex align-items-center justify-content-center"
style={{ backgroundColor: tagColor }}
onClick={this.togglePopover}
>
<i className="sf3-font sf3-font-down text-white"></i>
</span>
<Popover
target={`tag-${id}-color`}
isOpen={isPopoverOpen}
placement="bottom"
toggle={this.togglePopover}
className="tag-color-popover mw-100"
>
<PopoverBody className="p-2">
<div className="d-flex justify-content-between">
{colorList.map((item, index) => {
return (
<div key={index} className="tag-color-option mx-1">
<label className="colorinput">
<input name="color" type="radio" value={item} className="colorinput-input" defaultChecked={item == tagColor} onClick={this.selectTagColor} />
<span className="colorinput-color rounded-circle d-flex align-items-center justify-content-center" style={{ backgroundColor: item }}>
<i className="sf2-icon-tick color-selected"></i>
</span>
</label>
</div>
);
})
}
</div>
</PopoverBody>
</Popover>
</div>
);
}
}
TagColor.propTypes = tagColorPropTypes;
export default TagColor;

View File

@ -1,99 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import toaster from '../toast';
import '../../css/repo-tag.css';
const tagNamePropTypes = {
tag: PropTypes.object.isRequired,
repoID: PropTypes.string.isRequired
};
class TagName extends React.Component {
constructor(props) {
super(props);
this.state = {
tagName: this.props.tag.name,
isEditing: false
};
this.input = React.createRef();
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.tag.name !== this.props.tag.name) {
this.setState({
tagName: nextProps.tag.name,
});
}
}
toggleMode = () => {
this.setState({
isEditing: !this.state.isEditing
}, () => {
if (this.state.isEditing) {
this.input.current.focus();
}
});
};
updateTagName = (e) => {
const newName = e.target.value;
const { repoID, tag } = this.props;
const { id, color } = tag;
seafileAPI.updateRepoTag(repoID, id, newName, color).then(() => {
this.setState({
tagName: newName
});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
onInputKeyDown = (e) => {
if (e.key == 'Enter') {
this.toggleMode();
this.updateTagName(e);
}
else if (e.key == 'Escape') {
e.nativeEvent.stopImmediatePropagation();
this.toggleMode();
}
};
onInputBlur = (e) => {
this.toggleMode();
this.updateTagName(e);
};
render() {
const { isEditing, tagName } = this.state;
return (
<div className="mx-2 flex-fill d-flex">
{isEditing ?
<input
type="text"
ref={this.input}
defaultValue={tagName}
onBlur={this.onInputBlur}
onKeyDown={this.onInputKeyDown}
className="flex-fill form-control-sm form-control"
/> :
<span
onClick={this.toggleMode}
className="cursor-pointer flex-fill"
>{tagName}
</span>
}
</div>
);
}
}
TagName.propTypes = tagNamePropTypes;
export default TagName;

View File

@ -5,13 +5,6 @@ import { SimpleEditor } from '@seafile/seafile-editor';
import { gettext } from '../../utils/constants';
import SeahubModalHeader from '@/components/common/seahub-modal-header';
const propTypes = {
title: PropTypes.string,
content: PropTypes.string,
onCommit: PropTypes.func.isRequired,
onCloseEditorDialog: PropTypes.func.isRequired,
};
class TermsEditorDialog extends React.Component {
constructor(props) {
@ -22,10 +15,6 @@ class TermsEditorDialog extends React.Component {
this.editorRef = React.createRef();
}
static defaultProps = {
title: gettext('Terms'),
};
onKeyDown = (event) => {
event.stopPropagation();
};
@ -52,7 +41,7 @@ class TermsEditorDialog extends React.Component {
};
render() {
let { content, title } = this.props;
let { content, title = gettext('Terms') } = this.props;
return (
<Modal
isOpen={true}
@ -77,6 +66,11 @@ class TermsEditorDialog extends React.Component {
}
}
TermsEditorDialog.propTypes = propTypes;
TermsEditorDialog.propTypes = {
title: PropTypes.string,
content: PropTypes.string,
onCommit: PropTypes.func.isRequired,
onCloseEditorDialog: PropTypes.func.isRequired,
};
export default TermsEditorDialog;

View File

@ -13,17 +13,12 @@ const propTypes = {
class TermsPreviewDialog extends React.Component {
static defaultProps = {
title: gettext('Terms'),
};
toggle = () => {
this.props.onClosePreviewDialog();
};
render() {
let { title, content } = this.props;
let { title = gettext('Terms'), content } = this.props;
return (
<Modal
isOpen={true}

View File

@ -12,7 +12,7 @@ import { Utils } from '../../utils/utils';
import toaster from '../toast';
import UserSelect from '../user-select';
import { SeahubSelect } from '../common/select';
import Switch from '../common/switch';
import Switch from '../switch';
import '../../css/transfer-dialog.css';
const propTypes = {
@ -40,6 +40,7 @@ class TransferDialog extends React.Component {
reshare: false,
activeTab: !this.props.isDepAdminTransfer ? TRANS_USER : TRANS_DEPART
};
this.userSelect = React.createRef();
}
handleSelectChange = (option) => {
@ -157,7 +158,7 @@ class TransferDialog extends React.Component {
<TabPane tabId="transUser" role="tabpanel" id="transfer-user-panel">
<Label className='transfer-repo-label'>{gettext('Users')}</Label>
<UserSelect
ref="userSelect"
ref={this.userSelect}
isMulti={false}
placeholder={gettext('Select a user')}
onSelectChange={this.handleSelectChange}

View File

@ -11,9 +11,9 @@ import toaster from '../toast';
import '../../css/transfer-group-dialog.css';
const propTypes = {
groupID: PropTypes.string,
toggleTransferGroupDialog: PropTypes.func.isRequired,
onGroupChanged: PropTypes.func.isRequired
groupID: PropTypes.number.isRequired,
onGroupTransfered: PropTypes.func.isRequired,
toggleDialog: PropTypes.func.isRequired
};
class TransferGroupDialog extends React.Component {
@ -21,18 +21,14 @@ class TransferGroupDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedOption: null,
errMessage: '',
selectedOption: null
};
this.options = [];
}
handleSelectChange = (option) => {
this.setState({
selectedOption: option,
errMessage: '',
selectedOption: option
});
this.options = [];
};
transferGroup = () => {
@ -41,19 +37,21 @@ class TransferGroupDialog extends React.Component {
if (selectedOption && selectedOption[0]) {
email = selectedOption[0].email;
}
if (email) {
seafileAPI.transferGroup(this.props.groupID, email).then((res) => {
this.props.toggleTransferGroupDialog();
toaster.success(gettext('Group has been transfered'));
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
this.setState({ errMessage: errMessage });
});
if (!email) {
return false;
}
seafileAPI.transferGroup(this.props.groupID, email).then((res) => {
toaster.success(gettext('Group has been transfered'));
this.props.onGroupTransfered(res.data);
this.props.toggleDialog();
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
toggle = () => {
this.props.toggleTransferGroupDialog();
this.props.toggleDialog();
};
render() {
@ -63,12 +61,11 @@ class TransferGroupDialog extends React.Component {
<ModalBody>
<p>{gettext('Transfer group to')}</p>
<UserSelect
ref="userSelect"
ref={this.userSelect}
isMulti={false}
placeholder={gettext('Please enter 1 or more character')}
onSelectChange={this.handleSelectChange}
/>
<div className="error">{this.state.errMessage}</div>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{gettext('Close')}</Button>

View File

@ -1,18 +1,38 @@
.trash-dialog {
max-width: 1100px;
height: calc(100% - 56px);
overflow: hidden;
width: 100%;
height: 100%;
margin: 0;
}
.trash-dialog .modal-content {
height: 100%;
overflow: hidden;
border: none;
border-radius: 0;
}
@media (min-width: 768px) {
.trash-dialog {
max-width: 1100px;
height: calc(100% - 56px);
overflow: hidden;
margin: 1.75rem auto;
}
.trash-dialog .modal-content {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
}
.trash-dialog .modal-header {
align-items: center;
}
.trash-dialog .back-icon {
color: #999;
}
.trash-dialog .modal-header .button-control {
display: flex;
justify-content: center;
@ -45,6 +65,19 @@
background: #dfdfdf;
}
.trash-dialog .path-container {
border-bottom: 1px solid #eee;
height: 40px;
padding: 0 .5rem 1rem;
}
@media (min-width: 768px) {
.trash-dialog .path-container {
border-bottom: none;
padding: 0 0 1rem;
}
}
@media (max-width: 768px) {
.trash-dialog .modal-header .trash-dialog-old-page {
font-size: 14px;

View File

@ -188,25 +188,38 @@ class TrashDialog extends React.Component {
let title = gettext('{placeholder} Trash');
title = title.replace('{placeholder}', '<span class="op-target text-truncate mr-1">' + Utils.HTMLescape(repoFolderName) + '</span>');
const isDesktop = Utils.isDesktop();
return (
<>
<Modal className="trash-dialog" isOpen={showTrashDialog} toggle={toggleTrashDialog}>
<ModalHeader
close={
<div className="button-control">
<a className="trash-dialog-old-page" href={oldTrashUrl}>{gettext('Visit old version page')}</a>
{isDesktop && <a className="trash-dialog-old-page" href={oldTrashUrl}>{gettext('Visit old version page')}</a>}
{(enableUserCleanTrash && !showFolder && isRepoAdmin) &&
<button className="btn btn-secondary clean flex-shrink-0 ml-4" onClick={this.cleanTrash}>{gettext('Clean')}</button>
}
<button type="button" className="close seahub-modal-btn" aria-label={gettext('Close')} onClick={toggleTrashDialog}>
<span className="seahub-modal-btn-inner">
<i className="sf3-font sf3-font-x-01" aria-hidden="true"></i>
</span>
</button>
{isDesktop && (
<button type="button" className="close seahub-modal-btn" aria-label={gettext('Close')} onClick={toggleTrashDialog}>
<span className="seahub-modal-btn-inner">
<i className="sf3-font sf3-font-x-01" aria-hidden="true"></i>
</span>
</button>
)}
</div>
}
>
<div dangerouslySetInnerHTML={{ __html: title }}></div>
{!isDesktop &&
<span
role="button"
className="sf3-font sf3-font-arrow rotate-180 d-inline-block back-icon mr-2"
title={gettext('Back')}
aria-label={gettext('Back')}
onClick={toggleTrashDialog}
>
</span>
}
<span dangerouslySetInnerHTML={{ __html: title }}></span>
</ModalHeader>
<ModalBody>
{isLoading && <Loading />}
@ -214,15 +227,15 @@ class TrashDialog extends React.Component {
<EmptyTip text={gettext('No file')} className="m-0" />
}
{!isLoading && items.length > 0 &&
<div>
<div className="path-container dir-view-path mb-2">
<>
<div className="path-container dir-view-path mw-100 pb-2">
<span className="path-label mr-1">{gettext('Current path: ')}</span>
{showFolder ?
this.renderFolderPath() :
<span className="last-path-item" title={repoFolderName}>{repoFolderName}</span>
}
</div>
<Table repoID={repoID} data={this.state} renderFolder={this.renderFolder} />
<Table repoID={repoID} data={this.state} renderFolder={this.renderFolder} isDesktop={isDesktop} />
<Paginator
gotoPreviousPage={this.getPreviousPage}
gotoNextPage={this.getNextPage}
@ -232,7 +245,7 @@ class TrashDialog extends React.Component {
resetPerPage={this.resetPerPage}
noURLUpdate={true}
/>
</div>
</>
}
</ModalBody>
</Modal>

View File

@ -1,10 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import dayjs from 'dayjs';
import { DropdownItem } from 'reactstrap';
import { Utils, isMobile } from '../../../../../utils/utils';
import { gettext, siteRoot } from '../../../../../utils/constants';
import { seafileAPI } from '../../../../../utils/seafile-api';
import toaster from '../../../../toast';
import MobileItemMenu from '../../../../../components/mobile-item-menu';
class FileRecord extends React.Component {
@ -24,8 +26,12 @@ class FileRecord extends React.Component {
this.setState({ isIconShown: false });
};
restoreItem = (e) => {
onRestoreClicked = (e) => {
e.preventDefault();
this.restoreItem();
};
restoreItem = () => {
const { record } = this.props;
const { commit_id, parent_dir, obj_name, is_dir } = record;
const path = parent_dir + obj_name;
@ -55,40 +61,92 @@ class FileRecord extends React.Component {
};
render() {
const { record } = this.props;
const { record, isDesktop } = this.props;
const { restored, isIconShown } = this.state;
if (restored) return null;
return record.is_dir ? (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
<td className="pl-2 pr-2"><img src={Utils.getFolderIconUrl()} alt={gettext('Folder')} width="24" /></td>
<td><a href="#" onClick={this.renderFolder}>{record.obj_name}</a></td>
<td>{record.parent_dir}</td>
<td title={dayjs(record.deleted_time).format('dddd, MMMM D, YYYY h:mm:ss A')}>{dayjs(record.deleted_time).format('YYYY-MM-DD')}</td>
<td></td>
<td>
<a href="#" className={(isIconShown || isMobile) ? '' : 'invisible'} onClick={this.restoreItem} role="button">{gettext('Restore')}</a>
</td>
</tr>
) : (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
<td className="pl-2 pr-2"><img src={Utils.getFileIconUrl(record.obj_name)} alt={gettext('File')} width="24" /></td>
<td>
<a href={`${siteRoot}repo/${this.props.repoID}/trash/files/?obj_id=${record.obj_id}&commit_id=${record.commit_id}&base=${encodeURIComponent(record.parent_dir)}&p=${encodeURIComponent('/' + record.obj_name)}`} target="_blank" rel="noreferrer">
{record.obj_name}
</a>
</td>
<td>{record.parent_dir}</td>
<td title={dayjs(record.deleted_time).format('dddd, MMMM D, YYYY h:mm:ss A')}>
{dayjs(record.deleted_time).format('YYYY-MM-DD')}
</td>
<td>{Utils.bytesToSize(record.size)}</td>
<td>
<a href="#" className={(isIconShown || isMobile) ? '' : 'invisible'} onClick={this.restoreItem} role="button">{gettext('Restore')}</a>
</td>
</tr>
);
if (isDesktop) {
return record.is_dir ? (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
<td className="pl-2 pr-2"><img src={Utils.getFolderIconUrl()} alt={gettext('Folder')} width="24" /></td>
<td><a href="#" onClick={this.renderFolder}>{record.obj_name}</a></td>
<td>{record.parent_dir}</td>
<td title={dayjs(record.deleted_time).format('dddd, MMMM D, YYYY h:mm:ss A')}>{dayjs(record.deleted_time).format('YYYY-MM-DD')}</td>
<td></td>
<td>
<a href="#" className={(isIconShown || isMobile) ? '' : 'invisible'} onClick={this.onRestoreClicked} role="button">{gettext('Restore')}</a>
</td>
</tr>
) : (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
<td className="pl-2 pr-2"><img src={Utils.getFileIconUrl(record.obj_name)} alt={gettext('File')} width="24" /></td>
<td>
<a href={`${siteRoot}repo/${this.props.repoID}/trash/files/?obj_id=${record.obj_id}&commit_id=${record.commit_id}&base=${encodeURIComponent(record.parent_dir)}&p=${encodeURIComponent('/' + record.obj_name)}`} target="_blank" rel="noreferrer">
{record.obj_name}
</a>
</td>
<td>{record.parent_dir}</td>
<td title={dayjs(record.deleted_time).format('dddd, MMMM D, YYYY h:mm:ss A')}>
{dayjs(record.deleted_time).format('YYYY-MM-DD')}
</td>
<td>{Utils.bytesToSize(record.size)}</td>
<td>
<a href="#" className={(isIconShown || isMobile) ? '' : 'invisible'} onClick={this.onRestoreClicked} role="button">{gettext('Restore')}</a>
</td>
</tr>
);
} else { // for mobile
return record.is_dir ? (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
<td
onClick={this.renderFolder}
className="text-center"
>
<img src={Utils.getFolderIconUrl()} alt={gettext('Folder')} width="24" />
</td>
<td
onClick={this.renderFolder}
>
<a href="#" onClick={this.renderFolder}>{record.obj_name}</a>
<br />
<span className="item-meta-info">{record.parent_dir}</span>
<br />
<span className="item-meta-info" title={dayjs(record.deleted_time).format('dddd, MMMM D, YYYY h:mm:ss A')}>{dayjs(record.deleted_time).format('YYYY-MM-DD')}</span>
</td>
<td>
<MobileItemMenu>
<DropdownItem className="mobile-menu-item" onClick={this.restoreItem}>
{gettext('Restore')}
</DropdownItem>
</MobileItemMenu>
</td>
</tr>
) : (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
<td className="text-center"><img src={Utils.getFileIconUrl(record.obj_name)} alt={gettext('File')} width="24" /></td>
<td>
<a href={`${siteRoot}repo/${this.props.repoID}/trash/files/?obj_id=${record.obj_id}&commit_id=${record.commit_id}&base=${encodeURIComponent(record.parent_dir)}&p=${encodeURIComponent('/' + record.obj_name)}`} target="_blank" rel="noreferrer">
{record.obj_name}
</a>
<br />
<span className="item-meta-info">{record.parent_dir}</span>
<br />
<span className="item-meta-info mr-2">{Utils.bytesToSize(record.size)}</span>
<span className="item-meta-info">
{dayjs(record.deleted_time).format('YYYY-MM-DD')}
</span>
</td>
<td>
<MobileItemMenu>
<DropdownItem className="mobile-menu-item" onClick={this.restoreItem}>
{gettext('Restore')}
</DropdownItem>
</MobileItemMenu>
</td>
</tr>
);
}
}
}

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import FileRecord from './file-record';
const FileRecords = ({ records, repoID, renderFolder }) => {
const FileRecords = ({ records, repoID, renderFolder, isDesktop }) => {
if (!Array.isArray(records) || records.length === 0) return null;
return records.map((record, index) => {
return (
@ -11,6 +11,7 @@ const FileRecords = ({ records, repoID, renderFolder }) => {
record={record}
repoID={repoID}
renderFolder={renderFolder}
isDesktop={isDesktop}
/>
);
});

View File

@ -27,33 +27,68 @@ class FolderRecord extends React.Component {
};
render() {
const { commitID, baseDir, folderPath, record } = this.props;
const { commitID, baseDir, folderPath, record, isDesktop } = this.props;
return record.type == 'dir' ? (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
<td className="pl-2 pr-2"><img src={Utils.getFolderIconUrl()} alt={gettext('Folder')} width="24" /></td>
<td><a href="#" onClick={this.renderFolder}>{record.name}</a></td>
<td>{record.parent_dir}</td>
<td></td>
<td></td>
<td></td>
</tr>
) : (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
<td className="pl-2 pr-2">
<img src={Utils.getFileIconUrl(record.name)} alt={gettext('File')} width="24" />
</td>
<td>
<a href={`${siteRoot}repo/${this.props.repoID}/trash/files/?obj_id=${record.obj_id}&commit_id=${commitID}&base=${encodeURIComponent(baseDir)}&p=${encodeURIComponent(Utils.joinPath(folderPath, record.name))}`} target="_blank" rel="noreferrer">
{record.name}
</a>
</td>
<td>{record.parent_dir}</td>
<td></td>
<td>{Utils.bytesToSize(record.size)}</td>
<td></td>
</tr>
);
if (isDesktop) {
return record.type == 'dir' ? (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
<td className="pl-2 pr-2"><img src={Utils.getFolderIconUrl()} alt={gettext('Folder')} width="24" /></td>
<td><a href="#" onClick={this.renderFolder}>{record.name}</a></td>
<td>{record.parent_dir}</td>
<td></td>
<td></td>
<td></td>
</tr>
) : (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
<td className="pl-2 pr-2">
<img src={Utils.getFileIconUrl(record.name)} alt={gettext('File')} width="24" />
</td>
<td>
<a href={`${siteRoot}repo/${this.props.repoID}/trash/files/?obj_id=${record.obj_id}&commit_id=${commitID}&base=${encodeURIComponent(baseDir)}&p=${encodeURIComponent(Utils.joinPath(folderPath, record.name))}`} target="_blank" rel="noreferrer">
{record.name}
</a>
</td>
<td>{record.parent_dir}</td>
<td></td>
<td>{Utils.bytesToSize(record.size)}</td>
<td></td>
</tr>
);
} else { // for mobile
return record.type == 'dir' ? (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
<td
className="text-center"
onClick={this.renderFolder}
>
<img src={Utils.getFolderIconUrl()} alt={gettext('Folder')} width="24" />
</td>
<td onClick={this.renderFolder}>
<a href="#" onClick={this.renderFolder}>{record.name}</a>
<br />
<span className="item-meta-info">{record.parent_dir}</span>
</td>
<td></td>
</tr>
) : (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
<td className="text-center">
<img src={Utils.getFileIconUrl(record.name)} alt={gettext('File')} width="24" />
</td>
<td>
<a href={`${siteRoot}repo/${this.props.repoID}/trash/files/?obj_id=${record.obj_id}&commit_id=${commitID}&base=${encodeURIComponent(baseDir)}&p=${encodeURIComponent(Utils.joinPath(folderPath, record.name))}`} target="_blank" rel="noreferrer">
{record.name}
</a>
<br />
<span className="item-meta-info">{record.parent_dir}</span>
<br />
<span className="item-meta-info">{Utils.bytesToSize(record.size)}</span>
</td>
<td></td>
</tr>
);
}
}
}

Some files were not shown because too many files have changed in this diff Show More