+
this.ref = ref} id="sf-geolocation-map-container">
)}
diff --git a/frontend/src/metadata/components/metadata-details/settings/index.css b/frontend/src/metadata/components/metadata-details/settings/index.css
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/frontend/src/metadata/components/metadata-details/settings/index.js b/frontend/src/metadata/components/metadata-details/settings/index.js
new file mode 100644
index 00000000000..a3f99b8464f
--- /dev/null
+++ b/frontend/src/metadata/components/metadata-details/settings/index.js
@@ -0,0 +1,46 @@
+import React, { useMemo, useCallback, useState } from 'react';
+import Icon from '../../../../components/icon';
+import HideColumnPopover from '../../popover/hidden-column-popover';
+import { useMetadataDetails } from '../../../hooks';
+import { useMetadataStatus } from '../../../../hooks';
+
+import './index.css';
+
+const Settings = () => {
+ const [isShowSetter, setShowSetter] = useState(false);
+
+ const { enableMetadata } = useMetadataStatus();
+ const { modifyColumnOrder, modifyHiddenColumns, columns, canModifyDetails } = useMetadataDetails();
+ const hiddenColumns = useMemo(() => columns.filter(c => !c.shown).map(c => c.key), [columns]);
+
+ const onSetterToggle = useCallback(() => {
+ setShowSetter(!isShowSetter);
+ }, [isShowSetter]);
+ const target = useMemo(() => 'detail-control-settings-btn', []);
+
+ if (!enableMetadata) return null;
+ if (!canModifyDetails) return null;
+
+ return (
+ <>
+
+
+
+ {isShowSetter && (
+
+ )}
+ >
+ );
+
+};
+
+export default Settings;
diff --git a/frontend/src/metadata/components/metadata-details/utils.js b/frontend/src/metadata/components/metadata-details/utils.js
index 050c5c30c8b..0721f694f09 100644
--- a/frontend/src/metadata/components/metadata-details/utils.js
+++ b/frontend/src/metadata/components/metadata-details/utils.js
@@ -1,10 +1,10 @@
import { getNormalizedColumnType } from '../../utils/column';
import { getCellValueByColumn } from '../../utils/cell';
-import { NOT_DISPLAY_COLUMN_KEYS } from './constants';
+import { NOT_DISPLAY_COLUMN_KEYS, CellType, PRIVATE_COLUMN_KEY } from './constants';
export const normalizeFields = (fields) => {
if (!Array.isArray(fields) || fields.length === 0) return [];
- const validFields = fields.map((field) => {
+ let validFields = fields.map((field) => {
const { type, key, ...params } = field;
return {
...params,
@@ -13,11 +13,8 @@ export const normalizeFields = (fields) => {
width: 200,
};
}).filter(field => !NOT_DISPLAY_COLUMN_KEYS.includes(field.key));
- let displayFields = [];
- validFields.forEach(field => {
- displayFields.push(field);
- });
- return displayFields;
+ validFields.push({ key: PRIVATE_COLUMN_KEY.LOCATION, type: CellType.GEOLOCATION, width: 200 });
+ return validFields;
};
export {
diff --git a/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/hide-column.js b/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/hide-column.js
index e0c9fe6e2c7..c3b554c3201 100644
--- a/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/hide-column.js
+++ b/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/hide-column.js
@@ -1,110 +1,105 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, useRef } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
-import { DragSource, DropTarget } from 'react-dnd';
import { Icon, Switch } from '@seafile/sf-metadata-ui-component';
import { COLUMNS_ICON_CONFIG } from '../../../../constants';
-const dragSource = {
- beginDrag: props => {
- return { key: props.column.key, column: props.column };
- },
- endDrag(props, monitor) {
- const source = monitor.getItem();
- const didDrop = monitor.didDrop();
- let target = {};
- if (!didDrop) {
- return { source, target };
- }
- },
- isDragging(props) {
- const { columnIndex, currentIndex } = props;
- return currentIndex > columnIndex;
- }
-};
-const dropTarget = {
- drop(props, monitor) {
- const source = monitor.getItem();
- const { column: targetColumn } = props;
- if (targetColumn.key !== source.key && source.column.frozen === targetColumn.frozen) {
- const target = { key: targetColumn.key };
- props.onMove(source.key, target.key);
- }
- }
-};
-
-const dragCollect = (connect, monitor) => ({
- connectDragSource: connect.dragSource(),
- connectDragPreview: connect.dragPreview(),
- isDragging: monitor.isDragging(),
-});
-
-const dropCollect = (connect, monitor) => ({
- connectDropTarget: connect.dropTarget(),
- isOver: monitor.isOver(),
- canDrop: monitor.canDrop(),
- dragged: monitor.getItem(),
-});
-
const HideColumnItem = ({
- isOver,
- isDragging,
- canDrop,
- connectDragSource,
- connectDragPreview,
- connectDropTarget,
readOnly,
+ isHidden,
column,
columnIndex,
- isHidden,
+ draggingColumnKey,
+ draggingColumnIndex,
+ dragOverColumnKey,
+ updateDraggingKey,
+ updateDragOverKey,
onChange,
- onMouseEnter,
- onMouseLeave,
+ onMove,
}) => {
+ const ref = useRef(null);
+
+ const onDragStart = useCallback((event) => {
+ const dragData = JSON.stringify({ type: 'sf-metadata-filed-display-setting', column_key: column.key });
+ event.dataTransfer.setDragImage(ref.current, 10, 10);
+ event.dataTransfer.effectAllowed = 'move';
+ event.dataTransfer.setData('application/drag-sf-metadata-filed-display-setting', dragData);
+ updateDraggingKey(column.key);
+ }, [column, updateDraggingKey]);
+
+ const onDragEnter = useCallback(() => {
+ if (!draggingColumnKey) return;
+ updateDragOverKey(column.key);
+ }, [column, updateDragOverKey, draggingColumnKey]);
+
+ const onDragLeave = useCallback(() => {
+ if (!draggingColumnKey) return;
+ updateDragOverKey(null);
+ }, [updateDragOverKey, draggingColumnKey]);
+
+ const onDragOver = useCallback((event) => {
+ if (!draggingColumnKey) return;
+ event.preventDefault();
+ event.dataTransfer.dropEffect = 'move';
+ updateDragOverKey(column.key);
+ }, [column, updateDragOverKey, draggingColumnKey]);
+
+ const onDrop = useCallback((event) => {
+ event.stopPropagation();
+ let dragData = event.dataTransfer.getData('application/drag-sf-metadata-filed-display-setting');
+ if (!dragData) return false;
+ dragData = JSON.parse(dragData);
+ if (dragData.type !== 'sf-metadata-filed-display-setting' || !dragData.column_key) return false;
+ if (dragData.column_key !== column.key) {
+ onMove && onMove(dragData.column_key, column.key);
+ }
+ }, [column, onMove]);
+
+ const onDragEnd = useCallback(() => {
+ updateDraggingKey(null);
+ updateDragOverKey(null);
+ }, [updateDraggingKey, updateDragOverKey]);
const update = useCallback(() => {
if (readOnly) return;
onChange(column.key);
}, [readOnly, column, onChange]);
+ const isOver = dragOverColumnKey === column.key;
+
return (
- <>
- {connectDropTarget(
- connectDragPreview(
-
onMouseEnter(columnIndex)}
- onMouseLeave={onMouseLeave}
- >
- {!readOnly && (
- <>
- {connectDragSource(
-
-
-
- )}
- >
- )}
-
-
- {column.name}
- >
- )}
- onChange={update}
- switchClassName="hide-column-item-switch"
- />
-
- )
+
= columnIndex,
+ 'hide-column-can-drop': isOver && draggingColumnIndex < columnIndex,
+ 'dragging': draggingColumnKey === column.key
+ })}
+ onDrop={onDrop}
+ onDragEnter={onDragEnter}
+ onDragOver={onDragOver}
+ onDragLeave={onDragLeave}
+ onDragEnd={onDragEnd}
+ >
+ {!readOnly && (
+
+
+
)}
- >
+
+
+ {column.name}
+ >
+ )}
+ onChange={update}
+ switchClassName="hide-column-item-switch"
+ />
+
);
};
@@ -112,14 +107,14 @@ HideColumnItem.propTypes = {
readOnly: PropTypes.bool,
isHidden: PropTypes.bool,
columnIndex: PropTypes.number,
- currentIndex: PropTypes.number,
column: PropTypes.object.isRequired,
+ draggingColumnKey: PropTypes.string,
+ draggingColumnIndex: PropTypes.number,
+ dragOverColumnKey: PropTypes.string,
+ updateDraggingKey: PropTypes.func,
+ updateDragOverKey: PropTypes.func,
onChange: PropTypes.func.isRequired,
onMove: PropTypes.func,
- onMouseEnter: PropTypes.func,
- onMouseLeave: PropTypes.func,
};
-export default DropTarget('sfMetadataHiddenColumns', dropTarget, dropCollect)(
- DragSource('sfMetadataHiddenColumns', dragSource, dragCollect)(HideColumnItem)
-);
+export default HideColumnItem;
diff --git a/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/index.js b/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/index.js
index d1210be77dd..c4609101a00 100644
--- a/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/index.js
+++ b/frontend/src/metadata/components/popover/hidden-column-popover/hidden-columns/index.js
@@ -1,27 +1,29 @@
import React, { useCallback, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
-import { DropTarget } from 'react-dnd';
import HideColumn from './hide-column';
-import html5DragDropContext from '../../../../../pages/wiki2/wiki-nav/html5DragDropContext';
import { gettext } from '../../../../../utils/constants';
const HiddenColumns = ({ readOnly, columns, hiddenColumns, onChange, modifyColumnOrder }) => {
- const [currentIndex, setCurrentIndex] = useState(-1);
+ const [draggingColumnKey, setDraggingCellKey] = useState(null);
+ const [dragOverColumnKey, setDragOverCellKey] = useState(null);
const isEmpty = useMemo(() => {
if (!Array.isArray(columns) || columns.length === 0) return true;
return false;
}, [columns]);
- const onMouseEnter = useCallback((columnIndex) => {
- if (currentIndex === columnIndex) return;
- setCurrentIndex(columnIndex);
- }, [currentIndex]);
+ const updateDraggingKey = useCallback((cellKey) => {
+ if (cellKey === draggingColumnKey) return;
+ setDraggingCellKey(cellKey);
+ }, [draggingColumnKey]);
- const onMouseLeave = useCallback(() => {
- setCurrentIndex(-1);
- }, []);
+ const updateDragOverKey = useCallback((cellKey) => {
+ if (cellKey === dragOverColumnKey) return;
+ setDragOverCellKey(cellKey);
+ }, [dragOverColumnKey]);
+
+ const draggingColumnIndex = draggingColumnKey ? columns.findIndex(c => c.key === draggingColumnKey) : -1;
return (
@@ -32,13 +34,15 @@ const HiddenColumns = ({ readOnly, columns, hiddenColumns, onChange, modifyColum
key={column.key}
readOnly={readOnly}
columnIndex={columnIndex}
- currentIndex={currentIndex}
isHidden={!hiddenColumns.includes(column.key)}
column={column}
+ draggingColumnKey={draggingColumnKey}
+ draggingColumnIndex={draggingColumnIndex}
+ dragOverColumnKey={dragOverColumnKey}
onChange={onChange}
onMove={modifyColumnOrder}
- onMouseEnter={onMouseEnter}
- onMouseLeave={onMouseLeave}
+ updateDraggingKey={updateDraggingKey}
+ updateDragOverKey={updateDragOverKey}
/>
);
})}
@@ -54,8 +58,4 @@ HiddenColumns.propTypes = {
modifyColumnOrder: PropTypes.func,
};
-const DndHiddenColumns = DropTarget('sfMetadataHiddenColumns', {}, connect => ({
- connectDropTarget: connect.dropTarget()
-}))(HiddenColumns);
-
-export default html5DragDropContext(DndHiddenColumns);
+export default HiddenColumns;
diff --git a/frontend/src/metadata/components/popover/hidden-column-popover/index.css b/frontend/src/metadata/components/popover/hidden-column-popover/index.css
index 00438adf5ab..039f8259d2e 100644
--- a/frontend/src/metadata/components/popover/hidden-column-popover/index.css
+++ b/frontend/src/metadata/components/popover/hidden-column-popover/index.css
@@ -145,6 +145,10 @@
display: none;
}
+.sf-metadata-hide-columns-popover .hide-column-item.dragging {
+ background-color: #f5f5f5;
+}
+
.sf-metadata-hide-columns-popover .hide-column-item.hide-column-can-drop::after,
.sf-metadata-hide-columns-popover .hide-column-item.hide-column-can-drop-top::before {
content: '';
diff --git a/frontend/src/metadata/components/view-toolbar/kanban-view-toolbar/index.js b/frontend/src/metadata/components/view-toolbar/kanban-view-toolbar/index.js
index 3176fbccd79..9835eb6575a 100644
--- a/frontend/src/metadata/components/view-toolbar/kanban-view-toolbar/index.js
+++ b/frontend/src/metadata/components/view-toolbar/kanban-view-toolbar/index.js
@@ -67,13 +67,14 @@ const KanbanViewToolBar = ({
className="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-setting"
size={24}
role="button"
- aria-label="Setting"
+ aria-label={gettext('Settings')}
+ title={gettext('Settings')}
tabIndex={0}
onClick={onToggleKanbanSetting}
/>
{!isCustomPermission && (
-
diff --git a/frontend/src/metadata/hooks/index.js b/frontend/src/metadata/hooks/index.js
index a00eced1d9a..ca990d0368c 100644
--- a/frontend/src/metadata/hooks/index.js
+++ b/frontend/src/metadata/hooks/index.js
@@ -1,2 +1,3 @@
export { MetadataProvider, useMetadata } from './metadata';
export { CollaboratorsProvider, useCollaborators } from './collaborators';
+export { MetadataDetailsProvider, useMetadataDetails } from './metadata-details';
diff --git a/frontend/src/metadata/hooks/metadata-details.js b/frontend/src/metadata/hooks/metadata-details.js
new file mode 100644
index 00000000000..45ecccb5eee
--- /dev/null
+++ b/frontend/src/metadata/hooks/metadata-details.js
@@ -0,0 +1,211 @@
+import React, { useContext, useEffect, useCallback, useState, useMemo, useRef } from 'react';
+import metadataAPI from '../api';
+import { Utils } from '../../utils/utils';
+import toaster from '../../components/toast';
+import { useMetadataStatus } from '../../hooks/metadata-status';
+import { SYSTEM_FOLDERS } from '../../constants';
+import Column from '../model/column';
+import { normalizeFields } from '../components/metadata-details/utils';
+import { CellType, EVENT_BUS_TYPE, PREDEFINED_COLUMN_KEYS, PRIVATE_COLUMN_KEY } from '../constants';
+import { getCellValueByColumn, getOptionName, getColumnOptionNamesByIds, getColumnOptionNameById, getRecordIdFromRecord,
+ getFileObjIdFromRecord
+} from '../utils/cell';
+import tagsAPI from '../../tag/api';
+import { getColumnByKey, getColumnOptions, getColumnOriginName } from '../utils/column';
+
+const MetadataDetailsContext = React.createContext(null);
+
+export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, direntDetail, direntType, children }) => {
+ const { enableMetadata, detailsSettings, modifyDetailsSettings } = useMetadataStatus();
+
+ const [isLoading, setLoading] = useState(true);
+ const [record, setRecord] = useState(null);
+ const [originColumns, setOriginColumns] = useState([]);
+
+ const canModifyRecord = useMemo(() => repoInfo.permission !== 'admin' && repoInfo.permission !== 'rw' ? false : true, [repoInfo]);
+ const canModifyDetails = useMemo(() => repoInfo.is_admin, [repoInfo]);
+
+ const allColumnsRef = useRef([]);
+
+ const columns = useMemo(() => {
+ const orderAndHiddenColumns = detailsSettings?.columns || [];
+ if (!Array.isArray(orderAndHiddenColumns) || orderAndHiddenColumns.length === 0) {
+ return originColumns.map(c => ({ ...c, shown: true }));
+ }
+ const oldColumnsMap = orderAndHiddenColumns.reduce((pre, cur) => {
+ pre[cur.key] = true;
+ return pre;
+ }, {});
+ const columnsMap = originColumns.reduce((pre, cur) => {
+ pre[cur.key] = cur;
+ return pre;
+ }, {});
+ const exitColumnsOrder = orderAndHiddenColumns.map(c => {
+ const column = columnsMap[c.key];
+ if (column) return { ...c, ...column };
+ return null;
+ }).filter(c => c);
+ const newColumns = originColumns.filter(c => !oldColumnsMap[c.key]).map(c => ({ ...c, shown: false }));
+ return [...exitColumnsOrder, ...newColumns];
+ }, [originColumns, detailsSettings]);
+
+ const localRecordChanged = useCallback((recordId, updates) => {
+ if (getRecordIdFromRecord(record) !== recordId) return;
+ const newRecord = { ...record, ...updates };
+ setRecord(newRecord);
+ }, [record]);
+
+ const onChange = useCallback((fieldKey, newValue) => {
+ const field = getColumnByKey(originColumns, fieldKey);
+ const fileName = getColumnOriginName(field);
+ const recordId = getRecordIdFromRecord(record);
+ const fileObjId = getFileObjIdFromRecord(record);
+ let update = { [fileName]: newValue };
+ if (field.type === CellType.SINGLE_SELECT) {
+ update = { [fileName]: getColumnOptionNameById(field, newValue) };
+ } else if (field.type === CellType.MULTIPLE_SELECT) {
+ update = { [fileName]: newValue ? getColumnOptionNamesByIds(field, newValue) : [] };
+ }
+ metadataAPI.modifyRecord(repoID, recordId, update, fileObjId).then(res => {
+ setRecord({ ...record, ...update });
+ if (window?.sfMetadataContext?.eventBus) {
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, recordId, update);
+ }
+ }).catch(error => {
+ const errorMsg = Utils.getErrorMsg(error);
+ toaster.danger(errorMsg);
+ });
+ }, [repoID, record, originColumns]);
+
+ const modifyColumnData = useCallback((fieldKey, newData) => {
+ let newColumns = originColumns.slice(0);
+ let update;
+ metadataAPI.modifyColumnData(repoID, fieldKey, newData).then(res => {
+ const newColumn = new Column(res.data.column);
+ const fieldIndex = originColumns.findIndex(f => f.key === fieldKey);
+ newColumns[fieldIndex] = newColumn;
+ return newColumn;
+ }).then((newField) => {
+ const fileName = getColumnOriginName(newField);
+ const options = getColumnOptions(newField);
+ const newOption = options[options.length - 1];
+ update = { [fileName]: newOption.id };
+ if (!PREDEFINED_COLUMN_KEYS.includes(fieldKey) && newField.type === CellType.SINGLE_SELECT) {
+ update = { [fileName]: getOptionName(options, newOption.id) };
+ } else if (newField.type === CellType.MULTIPLE_SELECT) {
+ const oldValue = getCellValueByColumn(record, newField) || [];
+ update = { [fileName]: [...oldValue, newOption.name] };
+ }
+ return metadataAPI.modifyRecord(repoID, record._id, update, record._obj_id);
+ }).then(res => {
+ setOriginColumns(newColumns);
+ setRecord({ ...record, ...update });
+ }).catch(error => {
+ const errorMsg = Utils.getErrorMsg(error);
+ toaster.danger(errorMsg);
+ });
+ }, [repoID, record, originColumns]);
+
+ const updateFileTags = useCallback((updateRecords) => {
+ const { record_id, tags } = updateRecords[0];
+
+ tagsAPI.updateFileTags(repoID, [{ record_id, tags }]).then(res => {
+ const newValue = tags ? tags.map(id => ({ row_id: id, display_value: id })) : [];
+ const update = { [PRIVATE_COLUMN_KEY.TAGS]: newValue };
+ setRecord({ ...record, ...update });
+ if (window?.sfMetadataContext?.eventBus) {
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, record_id, update);
+ }
+ }).catch(error => {
+ const errorMsg = Utils.getErrorMsg(error);
+ toaster.danger(errorMsg);
+ });
+ }, [repoID, record]);
+
+ const saveColumns = useCallback((columns) => {
+ modifyDetailsSettings({ columns: columns.map(c => ({ key: c.key, shown: c.shown })) });
+ }, [modifyDetailsSettings]);
+
+ const modifyHiddenColumns = useCallback((hiddenColumns) => {
+ let newColumns = columns.slice(0);
+ newColumns = newColumns.map(c => ({ ...c, shown: !hiddenColumns.includes(c.key) }));
+ saveColumns(newColumns);
+ }, [columns, saveColumns]);
+
+ const modifyColumnOrder = useCallback((sourceColumnKey, targetColumnKey) => {
+ const targetColumnIndex = columns.findIndex(c => c.key === targetColumnKey);
+ const sourceColumn = columns.find(c => c.key === sourceColumnKey);
+ let newColumns = columns.slice(0);
+ newColumns = newColumns.filter(c => c.key !== sourceColumnKey);
+ newColumns.splice(targetColumnIndex, 0, sourceColumn);
+ saveColumns(newColumns);
+ }, [columns, saveColumns]);
+
+ useEffect(() => {
+ setLoading(true);
+ if (!dirent || !direntDetail || !enableMetadata || SYSTEM_FOLDERS.find(folderPath => path.startsWith(folderPath))) {
+ setRecord(null);
+ setOriginColumns([]);
+ setLoading(false);
+ return;
+ }
+
+ const dirName = Utils.getDirName(path);
+ const fileName = Utils.getFileName(path);
+ let parentDir = direntType === 'file' ? dirName : dirName.slice(0, dirName.length - fileName.length - 1);
+
+ if (!parentDir.startsWith('/')) {
+ parentDir = '/' + parentDir;
+ }
+ metadataAPI.getMetadataRecordInfo(repoID, parentDir, fileName).then(res => {
+ const { results, metadata } = res.data;
+ const record = Array.isArray(results) && results.length > 0 ? results[0] : {};
+ const columns = normalizeFields(metadata).map(field => new Column(field));
+ allColumnsRef.current = columns;
+ setRecord(record);
+ setOriginColumns(columns);
+ setLoading(false);
+ }).catch(error => {
+ const errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ setLoading(false);
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [enableMetadata, repoID, path, direntType, dirent, direntDetail]);
+
+ useEffect(() => {
+ const eventBus = window?.sfMetadataContext?.eventBus;
+ if (!eventBus) return;
+ const unsubscribeLocalRecordChanged = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, localRecordChanged);
+ return () => {
+ unsubscribeLocalRecordChanged();
+ };
+ }, [localRecordChanged]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useMetadataDetails = () => {
+ const context = useContext(MetadataDetailsContext);
+ if (!context) {
+ throw new Error('\'MetadataDetailsContext\' is null');
+ }
+ return context;
+};
diff --git a/frontend/src/metadata/utils/row/core.js b/frontend/src/metadata/utils/row/core.js
index 145cfcc471c..f69e806ee6d 100644
--- a/frontend/src/metadata/utils/row/core.js
+++ b/frontend/src/metadata/utils/row/core.js
@@ -27,6 +27,7 @@ const updateTableRowsWithRowsData = (tables, tableId, recordsData = []) => {
};
export const checkIsDir = (record) => {
+ if (!record) return false;
const isDir = record[PRIVATE_COLUMN_KEY.IS_DIR];
if (typeof isDir === 'string') {
return isDir.toUpperCase() === 'TRUE';
diff --git a/frontend/src/pages/markdown-editor/header-toolbar/header-toolbar.js b/frontend/src/pages/markdown-editor/header-toolbar/header-toolbar.js
index 690120e6e8d..7f23efd2859 100644
--- a/frontend/src/pages/markdown-editor/header-toolbar/header-toolbar.js
+++ b/frontend/src/pages/markdown-editor/header-toolbar/header-toolbar.js
@@ -17,7 +17,7 @@ import Dirent from '../../../../src/models/dirent';
import '../css/header-toolbar.css';
const { seafileCollabServer } = window.app.config;
-const { canDownloadFile, repoID, filePath } = window.app.pageOptions;
+const { canDownloadFile, repoID, filePath, isRepoAdmin } = window.app.pageOptions;
const propTypes = {
editorApi: PropTypes.object.isRequired,
@@ -95,7 +95,7 @@ class HeaderToolbar extends React.Component {
};
onArticleInfoToggle = () => {
- const repoInfo = { permission: this.currentDirent.permission };
+ const repoInfo = { permission: this.currentDirent.permission, is_admin: isRepoAdmin, };
const eventBus = EventBus.getInstance();
eventBus.dispatch(EXTERNAL_EVENTS.ON_ARTICLE_INFO_TOGGLE, this.isFileInfoShow ? null : {
diff --git a/frontend/src/pages/sdoc/sdoc-editor/index.css b/frontend/src/pages/sdoc/sdoc-editor/index.css
index 66584c35223..d152a3dd188 100644
--- a/frontend/src/pages/sdoc/sdoc-editor/index.css
+++ b/frontend/src/pages/sdoc/sdoc-editor/index.css
@@ -15,6 +15,16 @@
background-color: inherit;
}
+.sdoc-content-right-panel .detail-header .seafile-multicolor-icon-set-up {
+ fill: #999;
+ font-weight: 700;
+ font-size: 16px;
+}
+
+.sdoc-content-right-panel .detail-header .detail-control:hover .seafile-multicolor-icon-set-up {
+ fill: #5a5a5a;
+}
+
.sdoc-content-right-panel .detail-header .sdoc-sm-close {
color: #999;
font-weight: 700;
diff --git a/frontend/src/pages/sdoc/sdoc-editor/index.js b/frontend/src/pages/sdoc/sdoc-editor/index.js
index ea6beab5d63..5742a7d223e 100644
--- a/frontend/src/pages/sdoc/sdoc-editor/index.js
+++ b/frontend/src/pages/sdoc/sdoc-editor/index.js
@@ -16,7 +16,7 @@ const SdocEditor = () => {
const [currentDirent, setCurrentDirent] = useState(null);
const { collaborators } = useCollaborators();
const plugins = useMemo(() => {
- const { repoID, docPath, docPerm } = window.seafile;
+ const { repoID, docPath, docPerm, isRepoAdmin } = window.seafile;
return [
{
name: 'sdoc-info',
@@ -29,7 +29,7 @@ const SdocEditor = () => {
repoID={repoID}
path={docPath}
dirent={currentDirent}
- repoInfo={{ permission: docPerm }}
+ repoInfo={{ permission: docPerm, is_admin: isRepoAdmin }}
width={width - 1}
component={{
headerComponent: {
diff --git a/frontend/src/view-file-sdoc.js b/frontend/src/view-file-sdoc.js
index e30a45fa9a5..c1087f17b22 100644
--- a/frontend/src/view-file-sdoc.js
+++ b/frontend/src/view-file-sdoc.js
@@ -15,11 +15,12 @@ const {
repoID, repoName, repoEncrypted, parentDir, filePerm,
docPath, docName, docUuid, seadocAccessToken, seadocServerUrl, assetsUrl,
isSdocRevision, isPublished, originFilename, revisionCreatedAt, originFileVersion,
- originFilePath, originDocUuid, revisionId, isFreezed, mobileLogin
+ originFilePath, originDocUuid, revisionId, isFreezed, mobileLogin, isRepoAdmin
} = window.app.pageOptions;
window.seafile = {
repoID,
+ isRepoAdmin,
docPath,
docName,
docUuid,
@@ -52,7 +53,7 @@ window.seafile = {
mobileLogin,
};
-const repoInfo = { encrypted: repoEncrypted, permission: filePerm };
+const repoInfo = { encrypted: repoEncrypted, permission: filePerm, is_admin: isRepoAdmin };
ReactDom.render(
diff --git a/media/css/sf_font3/iconfont.css b/media/css/sf_font3/iconfont.css
index d0684766eaf..def6c7fe635 100644
--- a/media/css/sf_font3/iconfont.css
+++ b/media/css/sf_font3/iconfont.css
@@ -1,11 +1,11 @@
@font-face {
font-family: "sf3-font"; /* Project id 1230969 */
- src: url('iconfont.eot?t=1732614348756'); /* IE9 */
- src: url('iconfont.eot?t=1732614348756#iefix') format('embedded-opentype'), /* IE6-IE8 */
- url('iconfont.woff2?t=1732614348756') format('woff2'),
- url('iconfont.woff?t=1732614348756') format('woff'),
- url('iconfont.ttf?t=1732614348756') format('truetype'),
- url('iconfont.svg?t=1732614348756#sf3-font') format('svg');
+ src: url('./iconfont.eot?t=1733301127109'); /* IE9 */
+ src: url('./iconfont.eot?t=1733301127109#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('./iconfont.woff2?t=1733301127109') format('woff2'),
+ url('./iconfont.woff?t=1733301127109') format('woff'),
+ url('./iconfont.ttf?t=1733301127109') format('truetype'),
+ url('./iconfont.svg?t=1733301127109#sf3-font') format('svg');
}
.sf3-font {
diff --git a/media/css/sf_font3/iconfont.eot b/media/css/sf_font3/iconfont.eot
index d4fb6fdc870..4bb9fed7ba7 100644
Binary files a/media/css/sf_font3/iconfont.eot and b/media/css/sf_font3/iconfont.eot differ
diff --git a/media/css/sf_font3/iconfont.svg b/media/css/sf_font3/iconfont.svg
index afa771ec951..d40bdac5381 100644
--- a/media/css/sf_font3/iconfont.svg
+++ b/media/css/sf_font3/iconfont.svg
@@ -150,7 +150,7 @@
-
+
@@ -190,7 +190,7 @@
-
+
diff --git a/media/css/sf_font3/iconfont.ttf b/media/css/sf_font3/iconfont.ttf
index 62b77ed106f..0a1dae1a5d6 100644
Binary files a/media/css/sf_font3/iconfont.ttf and b/media/css/sf_font3/iconfont.ttf differ
diff --git a/media/css/sf_font3/iconfont.woff b/media/css/sf_font3/iconfont.woff
index fe823786dc8..77a4a0b7a7f 100644
Binary files a/media/css/sf_font3/iconfont.woff and b/media/css/sf_font3/iconfont.woff differ
diff --git a/media/css/sf_font3/iconfont.woff2 b/media/css/sf_font3/iconfont.woff2
index 5a753e5c8fa..48dbc69dbe0 100644
Binary files a/media/css/sf_font3/iconfont.woff2 and b/media/css/sf_font3/iconfont.woff2 differ
diff --git a/seahub/repo_metadata/apis.py b/seahub/repo_metadata/apis.py
index 215fb007b40..d8564d6835b 100644
--- a/seahub/repo_metadata/apis.py
+++ b/seahub/repo_metadata/apis.py
@@ -50,10 +50,14 @@ def get(self, request, repo_id):
is_enabled = False
is_tags_enabled = False
tags_lang = ''
+ details_settings = '{}'
try:
record = RepoMetadata.objects.filter(repo_id=repo_id).first()
if record and record.enabled:
is_enabled = True
+ details_settings = record.details_settings
+ if not details_settings:
+ details_settings = '{}'
if record and record.tags_enabled:
is_tags_enabled = True
tags_lang = record.tags_lang
@@ -66,6 +70,7 @@ def get(self, request, repo_id):
'enabled': is_enabled,
'tags_enabled': is_tags_enabled,
'tags_lang': tags_lang,
+ 'details_settings': details_settings
})
def put(self, request, repo_id):
@@ -148,6 +153,7 @@ def delete(self, request, repo_id):
record.enabled = False
record.face_recognition_enabled = False
record.tags_enabled = False
+ record.details_settings = '{}'
record.save()
RepoMetadataViews.objects.filter(repo_id=repo_id).delete()
except Exception as e:
@@ -157,6 +163,48 @@ def delete(self, request, repo_id):
return Response({'success': True})
+
+class MetadataDetailsSettingsView(APIView):
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated, )
+ throttle_classes = (UserRateThrottle, )
+
+ def put(self, request, repo_id):
+ settings = request.data.get('settings', {})
+ if not settings:
+ error_msg = 'settings invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ # resource check
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = f'Library {repo_id} not found.'
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ if not is_repo_admin(request.user.username, repo_id):
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ metadata = RepoMetadata.objects.filter(repo_id=repo_id).first()
+ if not metadata or not metadata.enabled:
+ error_msg = f'The metadata module is not enabled for repo {repo_id}.'
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ old_details_settings = metadata.details_settings if metadata.details_settings else '{}'
+ old_details_settings = json.loads(old_details_settings)
+ if not old_details_settings:
+ old_details_settings = {}
+
+ old_details_settings.update(settings)
+ try:
+ metadata.details_settings = json.dumps(old_details_settings)
+ metadata.save()
+ except Exception as e:
+ logger.exception(e)
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
+
+ return Response({'success': True})
+
class MetadataRecords(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, )
@@ -831,6 +879,7 @@ def post(self, request, repo_id):
try:
results = RepoMetadataViews.objects.move_view(repo_id, view_id, target_view_id)
except Exception as e:
+ logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
@@ -1184,7 +1233,7 @@ def post(self, request, repo_id):
return Response({'task_id': task_id})
def delete(self, request, repo_id):
- # recource check
+ # resource check
repo = seafile_api.get_repo(repo_id)
if not repo:
error_msg = 'Library %s not found.' % repo_id
@@ -1393,13 +1442,11 @@ def get(self, request, repo_id):
return Response(query_result)
-
-
def post(self, request, repo_id):
tags_data = request.data.get('tags_data', [])
if not tags_data:
- error_msg = f'Tags data is required.'
+ error_msg = 'Tags data is required.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
metadata = RepoMetadata.objects.filter(repo_id=repo_id).first()
@@ -1546,7 +1593,7 @@ def delete(self, request, repo_id):
tag_ids = request.data.get('tag_ids', [])
if not tag_ids:
- error_msg = f'Tag ids is required.'
+ error_msg = 'Tag ids is required.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
metadata = RepoMetadata.objects.filter(repo_id=repo_id).first()
@@ -1722,13 +1769,13 @@ def get(self, request, repo_id, tag_id):
tag_files_records = tag_query.get('results', [])
if not tag_files_records:
- return Response({ 'metadata': [], 'results': [] })
+ return Response({'metadata': [], 'results': []})
tag_files_record = tag_files_records[0]
tag_files_record_ids = tag_files_record.get(TAGS_TABLE.columns.file_links.name , [])
if not tag_files_record_ids:
- return Response({ 'metadata': [], 'results': [] })
+ return Response({'metadata': [], 'results': []})
tag_files_sql = 'SELECT `%s`, `%s`, `%s`, `%s`, `%s`, `%s` FROM %s WHERE `%s` IN (%s)' % (METADATA_TABLE.columns.id.name, METADATA_TABLE.columns.file_name.name, \
METADATA_TABLE.columns.parent_dir.name, METADATA_TABLE.columns.size.name, \
@@ -1743,4 +1790,3 @@ def get(self, request, repo_id, tag_id):
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response(tag_files_query)
-
diff --git a/seahub/repo_metadata/models.py b/seahub/repo_metadata/models.py
index 078f33f8867..5f0dbf9f930 100644
--- a/seahub/repo_metadata/models.py
+++ b/seahub/repo_metadata/models.py
@@ -68,6 +68,7 @@ class RepoMetadata(models.Model):
tags_enabled = models.BooleanField(db_index=True)
tags_lang = models.CharField(max_length=36)
last_face_cluster_time = models.DateTimeField(db_index=True, blank=True, null=True)
+ details_settings = models.TextField()
objects = RepoMetadataManager()
diff --git a/seahub/repo_metadata/urls.py b/seahub/repo_metadata/urls.py
index d44ddabbdb4..62d8f88e2ed 100644
--- a/seahub/repo_metadata/urls.py
+++ b/seahub/repo_metadata/urls.py
@@ -2,7 +2,7 @@
from .apis import MetadataRecords, MetadataManage, MetadataColumns, MetadataRecordInfo, \
MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataViewsDuplicateView, FacesRecords, \
FaceRecognitionManage, FacesRecord, MetadataExtractFileDetails, PeoplePhotos, MetadataTagsStatusManage, MetadataTags, \
- MetadataFileTags, MetadataTagFiles
+ MetadataFileTags, MetadataTagFiles, MetadataDetailsSettingsView
urlpatterns = [
re_path(r'^$', MetadataManage.as_view(), name='api-v2.1-metadata'),
@@ -24,6 +24,9 @@
re_path(r'^extract-file-details/$', MetadataExtractFileDetails.as_view(), name='api-v2.1-metadata-extract-file-details'),
+ # details settings
+ re_path(r'^details-settings/', MetadataDetailsSettingsView.as_view(), name='api-v2.1-metadata-details-settings'),
+
# tags api
re_path(r'^tags-status/$', MetadataTagsStatusManage.as_view(), name='api-v2.1-metadata-tags-status'),
re_path(r'^tags/$', MetadataTags.as_view(), name='api-v2.1-metadata-tags'),
diff --git a/seahub/templates/file_view_react.html b/seahub/templates/file_view_react.html
index 9f1270036f8..678ad5100a6 100644
--- a/seahub/templates/file_view_react.html
+++ b/seahub/templates/file_view_react.html
@@ -30,6 +30,7 @@
repoID: '{{ repo.id }}',
repoName: '{{ repo.name|escapejs }}',
repoEncrypted: {% if repo.encrypted %}true{% else %}false{% endif %},
+ isRepoAdmin: {% if is_repo_admin %}true{% else %}false{% endif %},
filePath: '{{ path|escapejs }}',
filePerm: '{{ file_perm }}',
fileType: '{{ filetype }}',
diff --git a/seahub/templates/markdown_file_view_react.html b/seahub/templates/markdown_file_view_react.html
index d18e7a64a80..334dba81502 100644
--- a/seahub/templates/markdown_file_view_react.html
+++ b/seahub/templates/markdown_file_view_react.html
@@ -39,6 +39,7 @@
repoID: '{{ repo.id }}',
repoName: '{{ repo.name|escapejs }}',
repoEncrypted: {% if repo.encrypted %}true{% else %}false{% endif %},
+ isRepoAdmin: {% if is_repo_admin %}true{% else %}false{% endif %},
filePath: '{{ path|escapejs }}',
fileName: '{{ filename|escapejs }}',
filePerm: '{{ file_perm }}',
diff --git a/seahub/views/file.py b/seahub/views/file.py
index 740fb559cb9..2dc89fcaef6 100644
--- a/seahub/views/file.py
+++ b/seahub/views/file.py
@@ -68,7 +68,7 @@
ONLINE_OFFICE_LOCK_OWNER, if_locked_by_online_office
from seahub.views import check_folder_permission, \
get_unencry_rw_repos_by_user
-from seahub.utils.repo import is_repo_owner, parse_repo_perm
+from seahub.utils.repo import is_repo_owner, parse_repo_perm, is_repo_admin
from seahub.group.utils import is_group_member
from seahub.thumbnail.utils import extract_xmind_image, get_thumbnail_src, \
XMIND_IMAGE_SIZE, get_share_link_thumbnail_src, get_thumbnail_image_path
@@ -567,6 +567,7 @@ def view_lib_file(request, repo_id, path):
'file_id': file_id,
'last_commit_id': repo.head_cmmt_id,
'is_repo_owner': is_repo_owner(request, repo_id, username),
+ 'is_repo_admin': is_repo_admin(username, repo_id),
'path': path,
'parent_dir': parent_dir,
'filename': filename,