mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-01 23:20:51 +00:00
feat: detail support tag (#7119)
* feat: detail support tag * feat: optimize code * feat: optimize code * feat: optimize code --------- Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
8
frontend/package-lock.json
generated
8
frontend/package-lock.json
generated
@@ -19,7 +19,7 @@
|
||||
"@seafile/sdoc-editor": "1.0.155",
|
||||
"@seafile/seafile-calendar": "0.0.28",
|
||||
"@seafile/seafile-editor": "1.0.126",
|
||||
"@seafile/sf-metadata-ui-component": "^0.0.53",
|
||||
"@seafile/sf-metadata-ui-component": "^0.0.56",
|
||||
"@uiw/codemirror-extensions-langs": "^4.19.4",
|
||||
"@uiw/codemirror-themes": "^4.23.5",
|
||||
"@uiw/react-codemirror": "^4.19.4",
|
||||
@@ -5024,9 +5024,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/sf-metadata-ui-component": {
|
||||
"version": "0.0.53",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.53.tgz",
|
||||
"integrity": "sha512-RzogKUvy0RkziPMkdjizPOfrMFFtkaZUaqvZT3RYggrZsPcJ68rbak2tI+4kIH7MUOQB4s43y5ygku0oxK7OEg==",
|
||||
"version": "0.0.56",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.56.tgz",
|
||||
"integrity": "sha512-sw3mbgUtXjEeqmi03azqTWzAaoW12CkT5FvB+bYa2wC1Re0o3t1kTFzqHVd/2NckOzHR2Qe9HFnQ8A0+5FOqGw==",
|
||||
"dependencies": {
|
||||
"@seafile/seafile-calendar": "0.0.28",
|
||||
"@seafile/seafile-editor": "^1.0.122",
|
||||
|
@@ -14,7 +14,7 @@
|
||||
"@seafile/sdoc-editor": "1.0.155",
|
||||
"@seafile/seafile-calendar": "0.0.28",
|
||||
"@seafile/seafile-editor": "1.0.126",
|
||||
"@seafile/sf-metadata-ui-component": "^0.0.53",
|
||||
"@seafile/sf-metadata-ui-component": "^0.0.56",
|
||||
"@uiw/codemirror-extensions-langs": "^4.19.4",
|
||||
"@uiw/codemirror-themes": "^4.23.5",
|
||||
"@uiw/react-codemirror": "^4.19.4",
|
||||
|
@@ -27,6 +27,11 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-name .sf-metadata-icon-tag {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.dirent-detail-item .dirent-detail-item-value {
|
||||
width: 200px;
|
||||
display: flex;
|
||||
|
@@ -35,7 +35,7 @@ const FileDetails = ({ repoID, repoInfo, path, direntDetail }) => {
|
||||
<DetailItem field={lastModifiedTimeField} className="sf-metadata-property-detail-formatter">
|
||||
<Formatter field={lastModifiedTimeField} value={direntDetail.last_modified}/>
|
||||
</DetailItem>
|
||||
{window.app.pageOptions.enableMetadataManagement && enableMetadata && (
|
||||
{enableMetadata && (
|
||||
<MetadataDetails repoID={repoID} filePath={path} repoInfo={repoInfo} direntType="file" />
|
||||
)}
|
||||
</>
|
||||
|
@@ -14,6 +14,7 @@ import OnlyofficeFileToolbar from './onlyoffice-file-toolbar';
|
||||
import EmbeddedFileDetails from '../dirent-detail/embedded-file-details';
|
||||
import { MetadataStatusProvider } from '../../hooks';
|
||||
import { CollaboratorsProvider } from '../../metadata';
|
||||
import { TagsProvider } from '../../tag/hooks';
|
||||
import Loading from '../loading';
|
||||
|
||||
import '../../css/file-view.css';
|
||||
@@ -153,13 +154,15 @@ class FileView extends React.Component {
|
||||
{isDetailsPanelOpen && (
|
||||
<MetadataStatusProvider repoID={repoID} >
|
||||
<CollaboratorsProvider repoID={repoID}>
|
||||
<EmbeddedFileDetails
|
||||
repoID={repoID}
|
||||
path={filePath}
|
||||
dirent={{ 'name': fileName, type: 'file' }}
|
||||
repoInfo={{ permission: filePerm }}
|
||||
onClose={this.toggleDetailsPanel}
|
||||
/>
|
||||
<TagsProvider repoID={repoID} repoInfo={{ permission: filePerm }}>
|
||||
<EmbeddedFileDetails
|
||||
repoID={repoID}
|
||||
path={filePath}
|
||||
dirent={{ 'name': fileName, type: 'file' }}
|
||||
repoInfo={{ permission: filePerm }}
|
||||
onClose={this.toggleDetailsPanel}
|
||||
/>
|
||||
</TagsProvider>
|
||||
</CollaboratorsProvider>
|
||||
</MetadataStatusProvider>
|
||||
)}
|
||||
|
@@ -5,6 +5,8 @@
|
||||
min-height: 35px;
|
||||
padding: 2px 10px;
|
||||
line-height: 1;
|
||||
max-height: 150px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.sf-metadata-delete-select-tags .sf-metadata-delete-select-tag {
|
||||
|
@@ -28,6 +28,12 @@
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-editor .sf-metadata-tags-editor-container .none-search-result {
|
||||
font-size: 14px;
|
||||
opacity: 0.5;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-editor .sf-metadata-tags-editor-tag-container {
|
||||
align-items: center;
|
||||
border-radius: 2px;
|
||||
|
@@ -24,7 +24,9 @@ const TagsEditor = forwardRef(({
|
||||
onPressTab,
|
||||
updateFileTags,
|
||||
}, ref) => {
|
||||
const { tagsData, addTag } = useTags();
|
||||
const { tagsData, addTag, context } = useTags();
|
||||
|
||||
const canAddTag = context.canAddTag();
|
||||
|
||||
const [value, setValue] = useState((oldValue || []).map(item => item.row_id).filter(item => getRowById(tagsData, item)));
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
@@ -44,9 +46,10 @@ const TagsEditor = forwardRef(({
|
||||
const displayTags = useMemo(() => getTagsByNameOrColor(tags, searchValue), [searchValue, tags]);
|
||||
|
||||
const isShowCreateBtn = useMemo(() => {
|
||||
if (!canAddTag) return false;
|
||||
if (!canEditData || !searchValue) return false;
|
||||
return !getTagByNameOrColor(displayTags, searchValue);
|
||||
}, [canEditData, displayTags, searchValue]);
|
||||
}, [canEditData, displayTags, searchValue, canAddTag]);
|
||||
|
||||
const style = useMemo(() => {
|
||||
return { width: column.width };
|
||||
|
@@ -1,45 +0,0 @@
|
||||
.sf-metadata-tags-formatter .sf-metadata-tags-operation-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-formatter .sf-metadata-tags-operation-container .sf-metadata-tags-operation-add-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background: #eceff4;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-formatter .sf-metadata-tags-operation-container .sf-metadata-tags-operation-add-btn:hover {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-formatter .sf-metadata-tags-operation-add-btn .sf-metadata-icon-add-table {
|
||||
font-size: 12px;
|
||||
fill: #212519;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-formatter .sf-metadata-tags-formatter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: max-content;
|
||||
min-height: 1rem;
|
||||
position: relative;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-formatter .sf-metadata-tags-formatter-container .sf-metadata-tag-formatter {
|
||||
margin-right: -0.5rem;
|
||||
border: 0.125rem solid #fff;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTags } from '../../../../tag/hooks';
|
||||
import { getRowById } from '../../../utils/table';
|
||||
import { getTagColor, getTagName } from '../../../../tag/utils/cell/core';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const FileTagsFormatter = ({ value: oldValue }) => {
|
||||
const { tagsData } = useTags();
|
||||
const value = useMemo(() => {
|
||||
if (!Array.isArray(oldValue)) return [];
|
||||
return oldValue.filter(item => getRowById(tagsData, item.row_id)).map(item => item.row_id);
|
||||
}, [oldValue, tagsData]);
|
||||
|
||||
return (
|
||||
<div className="sf-metadata-ui cell-formatter-container link-formatter sf-metadata-link-formatter sf-metadata-tags-formatter">
|
||||
{value.length > 0 && (
|
||||
<div className="sf-metadata-tags-formatter-container">
|
||||
{value.map((item) => {
|
||||
const tag = getRowById(tagsData, item);
|
||||
const tagColor = getTagColor(tag);
|
||||
const tagName = getTagName(tag);
|
||||
|
||||
return (
|
||||
<span className="sf-metadata-tag-formatter" key={item} style={{ backgroundColor: tagColor }} title={tagName}></span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
FileTagsFormatter.propTypes = {
|
||||
value: PropTypes.array,
|
||||
};
|
||||
|
||||
export default FileTagsFormatter;
|
@@ -4,6 +4,7 @@ import { Formatter } from '@seafile/sf-metadata-ui-component';
|
||||
import { useCollaborators } from '../../hooks';
|
||||
import { CellType } from '../../constants';
|
||||
import FileName from './file-name';
|
||||
import { useTags } from '../../../tag/hooks';
|
||||
|
||||
const CellFormatter = ({ readonly, value, field, record, ...params }) => {
|
||||
const { collaborators, collaboratorsCache, updateCollaboratorsCache, queryUser } = useCollaborators();
|
||||
@@ -18,13 +19,14 @@ const CellFormatter = ({ readonly, value, field, record, ...params }) => {
|
||||
queryUserAPI: queryUser,
|
||||
};
|
||||
}, [readonly, value, field, collaborators, collaboratorsCache, updateCollaboratorsCache, queryUser]);
|
||||
const { tagsData } = useTags();
|
||||
|
||||
if (field.type === CellType.FILE_NAME) {
|
||||
return (<FileName { ...props } { ...params } record={record} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<Formatter { ...props } { ...params } />
|
||||
<Formatter { ...props } { ...params } tagsData={tagsData} />
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -9,6 +9,7 @@ import CollaboratorEditor from './collaborator-editor';
|
||||
import DateEditor from './date-editor';
|
||||
import LongTextEditor from './long-text-editor';
|
||||
import RateEditor from './rate-editor';
|
||||
import TagsEditor from './tags-editor';
|
||||
import { lang } from '../../../utils/constants';
|
||||
import { CellType } from '../../constants';
|
||||
|
||||
@@ -47,6 +48,9 @@ const DetailEditor = ({ field, onChange: onChangeAPI, ...props }) => {
|
||||
case CellType.RATE: {
|
||||
return (<RateEditor { ...props } field={field} onChange={onChange} />);
|
||||
}
|
||||
case CellType.TAGS: {
|
||||
return (<TagsEditor { ...props } field={field} />);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
|
@@ -3,3 +3,7 @@
|
||||
width: 100%;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.sf-metadata-rate-property-detail-editor .sf-metadata-rate-editor {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
@@ -0,0 +1,46 @@
|
||||
.sf-metadata-tags-property-detail-editor {
|
||||
padding: 0 6px;
|
||||
min-height: 34px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-property-detail-editor:empty {
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-property-detail-editor .sf-metadata-delete-select-tags {
|
||||
min-height: 34px;
|
||||
border-bottom: none;
|
||||
background-color: inherit;
|
||||
border-radius: 0;
|
||||
border-radius: initial;
|
||||
padding: 2px 0px;
|
||||
max-height: fit-content;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-property-detail-editor .sf-metadata-delete-select-tags .sf-metadata-delete-select-tag {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-property-editor-popover .sf-metadata-delete-select-tags {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-property-editor-popover .popover {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-property-editor-popover .sf-metadata-tags-editor {
|
||||
position: unset;
|
||||
min-width: 200px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
opacity: 1;
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
border-radius: none;
|
||||
box-shadow: none;
|
||||
}
|
@@ -0,0 +1,117 @@
|
||||
import React, { useCallback, useMemo, useState, useRef, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Popover } from 'reactstrap';
|
||||
import { getRowById } from '../../../utils/table';
|
||||
import { getRecordIdFromRecord } from '../../../utils/cell';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import DeleteTag from '../../cell-editors/tags-editor/delete-tags';
|
||||
import { KeyCodes } from '../../../../constants';
|
||||
import { getEventClassName } from '../../../utils/common';
|
||||
import Editor from '../../cell-editors/tags-editor';
|
||||
import { useTags } from '../../../../tag/hooks';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const TagsEditor = ({ record, value, field, updateFileTags }) => {
|
||||
const ref = useRef(null);
|
||||
|
||||
const [showEditor, setShowEditor] = useState(false);
|
||||
|
||||
const { tagsData } = useTags();
|
||||
|
||||
const validValue = useMemo(() => {
|
||||
if (!Array.isArray(value) || value.length === 0) return [];
|
||||
return value.filter(item => getRowById(tagsData, item.row_id)).map(item => item.row_id);
|
||||
}, [value, tagsData]);
|
||||
|
||||
const onClick = useCallback((event) => {
|
||||
if (!event.target) return;
|
||||
const className = getEventClassName(event);
|
||||
if (className.indexOf('sf-metadata-search-tags') > -1) return;
|
||||
const dom = document.querySelector('.sf-metadata-tags-editor');
|
||||
if (!dom) return;
|
||||
if (dom.contains(event.target)) return;
|
||||
if (ref.current && !ref.current.contains(event.target) && showEditor) {
|
||||
setShowEditor(false);
|
||||
}
|
||||
}, [showEditor]);
|
||||
|
||||
const onHotKey = useCallback((event) => {
|
||||
if (event.keyCode === KeyCodes.Esc) {
|
||||
if (showEditor) {
|
||||
setShowEditor(false);
|
||||
}
|
||||
}
|
||||
}, [showEditor]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mousedown', onClick);
|
||||
document.addEventListener('keydown', onHotKey, true);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', onClick);
|
||||
document.removeEventListener('keydown', onHotKey, true);
|
||||
};
|
||||
}, [onClick, onHotKey]);
|
||||
|
||||
const openEditor = useCallback(() => {
|
||||
setShowEditor(true);
|
||||
}, []);
|
||||
|
||||
const onDeleteTag = useCallback((tagId, event) => {
|
||||
event && event.stopPropagation();
|
||||
event && event.nativeEvent && event.nativeEvent.stopImmediatePropagation();
|
||||
const newValue = validValue.slice(0);
|
||||
let optionIdx = validValue.indexOf(tagId);
|
||||
if (optionIdx > -1) {
|
||||
newValue.splice(optionIdx, 1);
|
||||
}
|
||||
const recordId = getRecordIdFromRecord(record);
|
||||
updateFileTags([{ record_id: recordId, tags: newValue, old_tags: value }]);
|
||||
}, [validValue, value, record, updateFileTags]);
|
||||
|
||||
const renderEditor = useCallback(() => {
|
||||
if (!showEditor) return null;
|
||||
const { width } = ref.current.getBoundingClientRect();
|
||||
return (
|
||||
<Popover
|
||||
target={ref}
|
||||
isOpen={true}
|
||||
placement="bottom-end"
|
||||
hideArrow={true}
|
||||
fade={false}
|
||||
className="sf-metadata-property-editor-popover sf-metadata-tags-property-editor-popover"
|
||||
boundariesElement={document.body}
|
||||
>
|
||||
<Editor
|
||||
saveImmediately={true}
|
||||
value={value}
|
||||
column={{ ...field, width: Math.max(width - 2, 200) }}
|
||||
record={record}
|
||||
updateFileTags={updateFileTags}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}, [showEditor, field, record, value, updateFileTags]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="sf-metadata-property-detail-editor sf-metadata-tags-property-detail-editor"
|
||||
placeholder={gettext('Empty')}
|
||||
ref={ref}
|
||||
onClick={openEditor}
|
||||
>
|
||||
{validValue.length > 0 && (<DeleteTag value={validValue} tags={tagsData} onDelete={onDeleteTag} />)}
|
||||
{renderEditor()}
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
TagsEditor.propTypes = {
|
||||
record: PropTypes.object,
|
||||
value: PropTypes.array,
|
||||
field: PropTypes.object,
|
||||
updateFileTags: PropTypes.func,
|
||||
};
|
||||
|
||||
export default TagsEditor;
|
@@ -21,7 +21,6 @@ export const NOT_DISPLAY_COLUMN_KEYS = [
|
||||
PRIVATE_COLUMN_KEY.LOCATION,
|
||||
PRIVATE_COLUMN_KEY.FACE_LINKS,
|
||||
PRIVATE_COLUMN_KEY.FACE_VECTORS,
|
||||
PRIVATE_COLUMN_KEY.TAGS,
|
||||
];
|
||||
|
||||
export const SYSTEM_FOLDERS = [
|
||||
|
@@ -74,3 +74,17 @@
|
||||
.dirent-detail-item-value:not(.editable) .sf-metadata-rate-formatter .sf-metadata-rate-item {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.dirent-detail-item-value:not(.editable) .sf-metadata-tags-formatter {
|
||||
padding: 8px 6px;
|
||||
}
|
||||
|
||||
.dirent-detail-item-value:not(.editable) .sf-metadata-tags-formatter .sf-metadata-ui-tags-container {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dirent-detail-item-value:not(.editable) .sf-metadata-tags-formatter .sf-metadata-ui-tags-container .sf-metadata-ui-tag {
|
||||
top: 0;
|
||||
cursor: default;
|
||||
}
|
||||
|
@@ -11,9 +11,11 @@ import { getCellValueByColumn, getOptionName, getColumnOptionNamesByIds, getColu
|
||||
import { normalizeFields } from './utils';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { CellType, EVENT_BUS_TYPE, PREDEFINED_COLUMN_KEYS, PRIVATE_COLUMN_KEY } from '../../constants';
|
||||
import { getColumnOptions, getColumnOriginName } from '../../utils/column';
|
||||
import { getColumnByKey, getColumnOptions, getColumnOriginName } from '../../utils/column';
|
||||
import { SYSTEM_FOLDERS } from './constants';
|
||||
import Location from './location';
|
||||
import { checkIsDir } from '../../utils/row';
|
||||
import tagsAPI from '../../../tag/api';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@@ -24,7 +26,7 @@ const MetadataDetails = ({ repoID, filePath, repoInfo, direntType, updateRecord
|
||||
|
||||
const onChange = useCallback((fieldKey, newValue) => {
|
||||
const { record, fields } = metadata;
|
||||
const field = fields.find(f => f.key === fieldKey);
|
||||
const field = getColumnByKey(fields, fieldKey);
|
||||
const fileName = getColumnOriginName(field);
|
||||
const recordId = getRecordIdFromRecord(record);
|
||||
const fileObjId = getFileObjIdFromRecord(record);
|
||||
@@ -82,6 +84,24 @@ const MetadataDetails = ({ repoID, filePath, repoInfo, direntType, updateRecord
|
||||
setMetadata(newMetadata);
|
||||
}, [metadata]);
|
||||
|
||||
const updateFileTags = useCallback((updateRecords) => {
|
||||
const { record } = metadata;
|
||||
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 };
|
||||
const newMetadata = { ...metadata, record: { ...record, ...update } };
|
||||
setMetadata(newMetadata);
|
||||
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, metadata]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
if (SYSTEM_FOLDERS.find(folderPath => filePath.startsWith(folderPath))) {
|
||||
@@ -98,7 +118,11 @@ const MetadataDetails = ({ repoID, filePath, repoInfo, direntType, updateRecord
|
||||
metadataAPI.getMetadataRecordInfo(repoID, parentDir, fileName).then(res => {
|
||||
const { results, metadata } = res.data;
|
||||
const record = Array.isArray(results) && results.length > 0 ? results[0] : {};
|
||||
const fields = normalizeFields(metadata).map(field => new Column(field));
|
||||
let fields = normalizeFields(metadata).map(field => new Column(field));
|
||||
const isDir = checkIsDir(record);
|
||||
if (isDir) {
|
||||
fields = fields.filter(field => field.type !== CellType.TAGS);
|
||||
}
|
||||
updateRecord && updateRecord(record);
|
||||
setMetadata({ record, fields });
|
||||
setLoading(false);
|
||||
@@ -134,9 +158,17 @@ const MetadataDetails = ({ repoID, filePath, repoInfo, direntType, updateRecord
|
||||
return (
|
||||
<DetailItem key={field.key} field={field} readonly={!canEdit}>
|
||||
{canEdit ? (
|
||||
<DetailEditor field={field} value={value} onChange={onChange} fields={fields} record={record} modifyColumnData={modifyColumnData} />
|
||||
<DetailEditor
|
||||
field={field}
|
||||
value={value}
|
||||
fields={fields}
|
||||
record={record}
|
||||
modifyColumnData={modifyColumnData}
|
||||
onChange={onChange}
|
||||
updateFileTags={updateFileTags}
|
||||
/>
|
||||
) : (
|
||||
<CellFormatter field={field} value={value} emptyTip={gettext('Empty')} className="sf-metadata-property-detail-formatter" />
|
||||
<CellFormatter readonly={true} field={field} value={value} emptyTip={gettext('Empty')} className="sf-metadata-property-detail-formatter" />
|
||||
)}
|
||||
</DetailItem>
|
||||
);
|
||||
|
@@ -5,7 +5,6 @@ import CheckboxEditor from '../../../../../../components/cell-editors/checkbox-e
|
||||
import RateEditor from '../../../../../../components/cell-editors/rate-editor';
|
||||
import { canEditCell } from '../../../../../../utils/column';
|
||||
import { CellType } from '../../../../../../constants';
|
||||
import FileTagsFormatter from '../../../../../../components/cell-formatter/file-tags-formatter';
|
||||
|
||||
const Formatter = ({ isCellSelected, field, value, onChange, record }) => {
|
||||
const { type } = field;
|
||||
@@ -17,10 +16,6 @@ const Formatter = ({ isCellSelected, field, value, onChange, record }) => {
|
||||
return (<RateEditor isCellSelected={isCellSelected} value={value} field={field} onChange={onChange} />);
|
||||
}
|
||||
|
||||
if (field.type === CellType.TAGS) {
|
||||
return (<FileTagsFormatter isCellSelected={isCellSelected} field={field} readonly={!cellEditAble} value={value} record={record} />);
|
||||
}
|
||||
|
||||
return (<CellFormatter readonly={true} isCellSelected={isCellSelected} value={value} field={field} record={record} />);
|
||||
};
|
||||
|
||||
|
@@ -183,6 +183,7 @@ export const TagsProvider = ({ repoID, currentPath, selectTagsView, children, ..
|
||||
}, [tagsData, modifyLocalTags]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!handelSelectTag) return;
|
||||
if (isLoading) return;
|
||||
const { search } = window.location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
@@ -206,6 +207,7 @@ export const TagsProvider = ({ repoID, currentPath, selectTagsView, children, ..
|
||||
}, [isLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentPath) return;
|
||||
if (!currentPath.includes('/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES + '/')) return;
|
||||
const currentTagId = currentPath.split('/').pop();
|
||||
if (currentTagId === ALL_TAGS_ID) {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useState, useRef, useEffect } from 'react';
|
||||
import { useTagView } from '../../hooks';
|
||||
import { useTagView, useTags } from '../../hooks';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import TagFile from './tag-file';
|
||||
import { getRecordIdFromRecord } from '../../../metadata/utils/cell';
|
||||
@@ -10,6 +10,7 @@ import './index.css';
|
||||
|
||||
const TagFiles = () => {
|
||||
const { tagFiles, repoID, repoInfo } = useTagView();
|
||||
const { tagsData } = useTags();
|
||||
const [selectedFiles, setSelectedFiles] = useState(null);
|
||||
const [isImagePreviewerVisible, setImagePreviewerVisible] = useState(false);
|
||||
const [containerWidth, setContainerWidth] = useState(0);
|
||||
@@ -119,6 +120,7 @@ const TagFiles = () => {
|
||||
repoID={repoID}
|
||||
isSelected={selectedFiles && selectedFiles.includes(fileId)}
|
||||
file={file}
|
||||
tagsData={tagsData}
|
||||
onSelectFile={onSelectFile}
|
||||
reSelectFiles={reSelectFiles}
|
||||
openImagePreview={openImagePreview}
|
||||
|
@@ -1,10 +1,15 @@
|
||||
.tag-list-title .sf-metadata-tags-formatter .sf-metadata-tag-formatter {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
top: 0;
|
||||
.sf-metadata-tags-main .tag-list-title .sf-metadata-ui-tags-container {
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tag-list-title .sf-metadata-tags-formatter .sf-metadata-tag-formatter:last-child {
|
||||
.tag-list-title .sf-metadata-ui-tags-container .sf-metadata-ui-tag {
|
||||
height: 16px !important;
|
||||
width: 16px !important;
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
.tag-list-title .sf-metadata-ui-tags-container .sf-metadata-ui-tag:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@@ -18,8 +23,3 @@
|
||||
.sf-metadata-tags-main .table-container td.name a {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-main .tag-list-title .sf-metadata-tags-formatter {
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@@ -3,19 +3,19 @@ import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { FileTagsFormatter } from '@seafile/sf-metadata-ui-component';
|
||||
import { gettext, siteRoot, thumbnailDefaultSize } from '../../../../utils/constants';
|
||||
import { getParentDirFromRecord, getRecordIdFromRecord, getFileNameFromRecord, getFileSizedFromRecord,
|
||||
getFileMTimeFromRecord, getTagsFromRecord, getFilePathByRecord,
|
||||
} from '../../../../metadata/utils/cell';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import FileTagsFormatter from '../../../../metadata/components/cell-formatter/file-tags-formatter';
|
||||
import { openFile } from '../../../../metadata/utils/open-file';
|
||||
|
||||
import './index.css';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const TagFile = ({ isSelected, repoID, file, onSelectFile, reSelectFiles, openImagePreview }) => {
|
||||
const TagFile = ({ isSelected, repoID, file, tagsData, onSelectFile, reSelectFiles, openImagePreview }) => {
|
||||
const [highlight, setHighlight] = useState(false);
|
||||
const [isIconLoadError, setIconLoadError] = useState(false);
|
||||
|
||||
@@ -112,7 +112,7 @@ const TagFile = ({ isSelected, repoID, file, onSelectFile, reSelectFiles, openIm
|
||||
<a href={path} onClick={handelClickFileName}>{name}</a>
|
||||
</td>
|
||||
<td className="tag-list-title">
|
||||
<FileTagsFormatter value={tags} />
|
||||
<FileTagsFormatter value={tags} tagsData={tagsData} className="sf-metadata-tags-formatter" />
|
||||
</td>
|
||||
<td className="operation"></td>
|
||||
<td className="file-size">{size || ''}</td>
|
||||
|
@@ -7,6 +7,7 @@ import Loading from './components/loading';
|
||||
import SdocEditor from './pages/sdoc/sdoc-editor';
|
||||
import { MetadataStatusProvider } from './hooks';
|
||||
import { CollaboratorsProvider } from './metadata';
|
||||
import { TagsProvider } from './tag/hooks';
|
||||
|
||||
const { serviceURL, avatarURL, siteRoot, lang, mediaUrl, isPro } = window.app.config;
|
||||
const { username, name } = window.app.userInfo;
|
||||
@@ -55,7 +56,9 @@ ReactDom.render(
|
||||
<Suspense fallback={<Loading />}>
|
||||
<MetadataStatusProvider repoID={repoID}>
|
||||
<CollaboratorsProvider repoID={repoID}>
|
||||
<SdocEditor />
|
||||
<TagsProvider repoID={repoID} repoInfo={{ permission: filePerm }}>
|
||||
<SdocEditor />
|
||||
</TagsProvider>
|
||||
</CollaboratorsProvider>
|
||||
</MetadataStatusProvider>
|
||||
</Suspense>
|
||||
|
@@ -231,12 +231,12 @@ def remove_tags_table(metadata_server_api):
|
||||
tables = metadata.get('tables', [])
|
||||
for table in tables:
|
||||
if table['name'] == TAGS_TABLE.name:
|
||||
metadata_server_api.delete_table(table['id'])
|
||||
metadata_server_api.delete_table(table['id'], True)
|
||||
elif table['name'] == METADATA_TABLE.name:
|
||||
columns = table.get('columns', [])
|
||||
for column in columns:
|
||||
if column['key'] in [METADATA_TABLE.columns.tags.key]:
|
||||
metadata_server_api.delete_column(table['id'], column['key'])
|
||||
metadata_server_api.delete_column(table['id'], column['key'], True)
|
||||
|
||||
|
||||
def get_file_download_token(repo_id, file_id, username):
|
||||
|
Reference in New Issue
Block a user