diff --git a/frontend/src/components/date-and-time-picker.js b/frontend/src/components/date-and-time-picker.js
index 6e5b81ffc8..f7cb8be8e1 100644
--- a/frontend/src/components/date-and-time-picker.js
+++ b/frontend/src/components/date-and-time-picker.js
@@ -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
};
diff --git a/frontend/src/components/search/search-filters/filter-by-creator.js b/frontend/src/components/search/search-filters/filter-by-creator.js
new file mode 100644
index 0000000000..a5ad134bba
--- /dev/null
+++ b/frontend/src/components/search/search-filters/filter-by-creator.js
@@ -0,0 +1,132 @@
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
+import { gettext } from '../../../utils/constants';
+import { Utils } from '../../../utils/utils';
+import UserItem from './user-item';
+import { seafileAPI } from '../../../utils/seafile-api';
+import ModalPortal from '../../modal-portal';
+import toaster from '../../toast';
+
+const FilterByCreator = ({ repoID, onSelect }) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [options, setOptions] = useState([]);
+ const [value, setValue] = useState([]);
+ const [searchValue, setSearchValue] = useState('');
+
+ const label = useMemo(() => {
+ if (!value || value.length === 0) return gettext('Creator');
+ const label = [];
+ value.forEach((v) => {
+ const option = options.find((o) => o.key === v);
+ if (option) {
+ label.push(option.name);
+ }
+ });
+ return `Creator: ${label.join(',')}`;
+ }, [options, value]);
+
+ const toggle = useCallback((e) => {
+ setIsOpen(!isOpen);
+ }, [isOpen]);
+
+ const displayOptions = useMemo(() => {
+ if (!searchValue) return options;
+ return options.filter((option) => {
+ return option.name.toLowerCase().includes(searchValue.toLowerCase());
+ });
+ }, [options, searchValue]);
+
+ const onSelectOption = useCallback((e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const option = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
+ let updated = [...value];
+ if (!updated.includes(option)) {
+ updated = [...updated, option];
+ } else {
+ updated = updated.filter((v) => v !== option);
+ }
+ setValue(updated);
+ onSelect('creator', updated);
+ setSearchValue('');
+ }, [value, onSelect]);
+
+ const handleCancel = useCallback((v) => {
+ const updated = value.filter((item) => item !== v);
+ setValue(updated);
+ onSelect('creator', updated);
+ }, [value, onSelect]);
+
+ useEffect(() => {
+ const getUsers = async () => {
+ try {
+ const res = await seafileAPI.listRepoRelatedUsers(repoID);
+ const users = res.data.user_list;
+ const options = users.map((user) => {
+ return {
+ key: user.email,
+ value: user.email,
+ name: user.name,
+ label: