mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-22 03:47:09 +00:00
feat: sf metadata display (#6249)
* feat: sf metadata display * feat: update code * feat: update code * feat: lock react version * feat: bug * feat: optimize code * feat: update transalte * feat: update transalte * feat: rebase code * Feat: update code * Feat: update code --------- Co-authored-by: 杨国璇 <ygx@192.168.1.5> Co-authored-by: 杨国璇 <ygx@Hello-word.local> Co-authored-by: 杨国璇 <ygx@192.168.1.13>
This commit is contained in:
@@ -7,6 +7,7 @@ import { Utils } from '../../utils/utils';
|
||||
import { InternalLinkOperation } from '../operations';
|
||||
import DirOperationToolBar from '../../components/toolbar/dir-operation-toolbar';
|
||||
import ViewFileToolbar from '../../components/toolbar/view-file-toolbar';
|
||||
import { PRIVATE_FILE_TYPE } from '../../constants';
|
||||
|
||||
const propTypes = {
|
||||
repoID: PropTypes.string.isRequired,
|
||||
@@ -62,6 +63,15 @@ class DirPath extends React.Component {
|
||||
return null;
|
||||
}
|
||||
if (index === (pathList.length - 1)) {
|
||||
if (item === PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES) {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<span className="path-split">/</span>
|
||||
<span className="path-item">{gettext('File extended properties')}</span>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<span className="path-split">/</span>
|
||||
@@ -115,6 +125,13 @@ class DirPath extends React.Component {
|
||||
return pathElem;
|
||||
};
|
||||
|
||||
isViewMetadata = () => {
|
||||
const { currentPath } = this.props;
|
||||
const path = currentPath[currentPath.length - 1] === '/' ? currentPath.slice(0, currentPath.length - 1) : currentPath;
|
||||
const pathList = path.split('/');
|
||||
return pathList[pathList.length - 1] === PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES;
|
||||
};
|
||||
|
||||
render() {
|
||||
let { currentPath, repoName, fileTags } = this.props;
|
||||
let pathElem = this.turnPathToLink(currentPath);
|
||||
@@ -172,7 +189,7 @@ class DirPath extends React.Component {
|
||||
<span className="path-item" data-path="/" onClick={this.onPathClick} role="button">{repoName}</span>
|
||||
}
|
||||
{pathElem}
|
||||
{this.props.isViewFile && (
|
||||
{this.props.isViewFile && !this.isViewMetadata() && (
|
||||
<InternalLinkOperation repoID={this.props.repoID} path={this.props.currentPath}/>
|
||||
)}
|
||||
{(this.props.isViewFile && fileTags.length !== 0) &&
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SeafileMetadata } from '../../metadata';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { gettext, siteRoot } from '../../utils/constants';
|
||||
import { gettext, siteRoot, lang, mediaUrl } from '../../utils/constants';
|
||||
import SeafileMarkdownViewer from '../seafile-markdown-viewer';
|
||||
|
||||
const propTypes = {
|
||||
@@ -48,6 +49,22 @@ class DirColumnFile extends React.Component {
|
||||
<div className="message err-tip">{gettext('File does not exist.')}</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.content === '__sf-metadata') {
|
||||
window.sfMetadata = {
|
||||
siteRoot,
|
||||
lang,
|
||||
mediaUrl,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-100 h-100 o-hidden d-flex" style={{ paddingRight: 10, flexDirection: 'column', alignItems: 'center' }}>
|
||||
<div className="" style={{ width: '100%', height: 10, zIndex: 7, transform: 'translateZ(1000px)', position: 'relative', background: '#fff' }}></div>
|
||||
<SeafileMetadata repoID={this.props.repoID} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SeafileMarkdownViewer
|
||||
isTOCShow={false}
|
||||
|
@@ -63,7 +63,7 @@ class DirColumnNav extends React.Component {
|
||||
|
||||
onNodeClick = (node) => {
|
||||
this.setState({opNode: node});
|
||||
if (Utils.imageCheck(node.object.name)) {
|
||||
if (Utils.imageCheck(node?.object?.name || '')) {
|
||||
this.showNodeImagePopup(node);
|
||||
return;
|
||||
}
|
||||
@@ -268,7 +268,7 @@ class DirColumnNav extends React.Component {
|
||||
repoID={this.props.repoID}
|
||||
/>
|
||||
</TreeSection>
|
||||
<DirViews repoID={this.props.repoID} userPerm={this.props.userPerm} />
|
||||
<DirViews repoID={this.props.repoID} currentPath={this.props.currentPath} userPerm={this.props.userPerm} onNodeClick={this.onNodeClick}/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -3,14 +3,13 @@ import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import TreeSection from '../../tree-section';
|
||||
import MetadataStatusManagementDialog from '../../metadata-manage/metadata-status-manage-dialog';
|
||||
import metadataManagerAPI from '../../metadata-manage/api';
|
||||
import { MetadataStatusManagementDialog, MetadataTreeView } from '../../../metadata';
|
||||
import metadataAPI from '../../../metadata/api';
|
||||
import toaster from '../../toast';
|
||||
import MetadataViews from '../../metadata-manage/metadata-views';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const DirViews = ({ userPerm, repoID }) => {
|
||||
const DirViews = ({ userPerm, repoID, currentPath, onNodeClick }) => {
|
||||
const enableMetadataManagement = useMemo(() => {
|
||||
return window.app.pageOptions.enableMetadataManagement;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -33,7 +32,7 @@ const DirViews = ({ userPerm, repoID }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const repoMetadataManagementEnabledStatusRes = metadataManagerAPI.getRepoMetadataManagementEnabledStatus(repoID);
|
||||
const repoMetadataManagementEnabledStatusRes = metadataAPI.getMetadataStatus(repoID);
|
||||
Promise.all([repoMetadataManagementEnabledStatusRes]).then(results => {
|
||||
const [repoMetadataManagementEnabledStatusRes] = results;
|
||||
setMetadataStatus(repoMetadataManagementEnabledStatusRes.data.enabled);
|
||||
@@ -65,7 +64,7 @@ const DirViews = ({ userPerm, repoID }) => {
|
||||
return (
|
||||
<>
|
||||
<TreeSection title={gettext('Views')} moreKey={{ name: 'views' }} moreOperations={moreOperations} moreOperationClick={moreOperationClick}>
|
||||
{!loading && metadataStatus && (<MetadataViews repoID={repoID} />)}
|
||||
{!loading && metadataStatus && (<MetadataTreeView repoID={repoID} currentPath={currentPath} onNodeClick={onNodeClick} />)}
|
||||
</TreeSection>
|
||||
{showMetadataStatusManagementDialog && (
|
||||
<MetadataStatusManagementDialog value={metadataStatus} repoID={repoID} toggle={closeMetadataManagementDialog} submit={toggleMetadataStatus} />
|
||||
@@ -77,6 +76,8 @@ const DirViews = ({ userPerm, repoID }) => {
|
||||
DirViews.propTypes = {
|
||||
userPerm: PropTypes.string,
|
||||
repoID: PropTypes.string,
|
||||
currentPath: PropTypes.string,
|
||||
onNodeClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default DirViews;
|
||||
|
@@ -1,98 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import cookie from 'react-cookies';
|
||||
import { siteRoot } from '../../utils/constants';
|
||||
|
||||
class MetadataManagerAPI {
|
||||
init({ server, username, password, token }) {
|
||||
this.server = server;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.token = token; //none
|
||||
if (this.token && this.server) {
|
||||
this.req = axios.create({
|
||||
baseURL: this.server,
|
||||
headers: { 'Authorization': 'Token ' + this.token },
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
initForSeahubUsage({ siteRoot, xcsrfHeaders }) {
|
||||
if (siteRoot && siteRoot.charAt(siteRoot.length-1) === '/') {
|
||||
var server = siteRoot.substring(0, siteRoot.length-1);
|
||||
this.server = server;
|
||||
} else {
|
||||
this.server = siteRoot;
|
||||
}
|
||||
|
||||
this.req = axios.create({
|
||||
headers: {
|
||||
'X-CSRFToken': xcsrfHeaders,
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
_sendPostRequest(url, form) {
|
||||
if (form.getHeaders) {
|
||||
return this.req.post(url, form, {
|
||||
headers:form.getHeaders()
|
||||
});
|
||||
} else {
|
||||
return this.req.post(url, form);
|
||||
}
|
||||
}
|
||||
|
||||
getRepoMetadataManagementEnabledStatus(repoID) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/';
|
||||
return this.req.get(url);
|
||||
}
|
||||
|
||||
openRepoMetadataManagement(repoID) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/';
|
||||
return this.req.put(url);
|
||||
}
|
||||
|
||||
closeRepoMetadataManagement(repoID) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/';
|
||||
return this.req.delete(url);
|
||||
}
|
||||
|
||||
getMetadataRecords(repoID, params) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/records/';
|
||||
return this.req.get(url, {params: params});
|
||||
}
|
||||
|
||||
addMetadataRecords(repoID, parentDir, name) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/records/';
|
||||
const data = {
|
||||
'parent_dir': parentDir,
|
||||
'name': name,
|
||||
};
|
||||
return this.req.post(url, data);
|
||||
}
|
||||
|
||||
updateMetadataRecord(repoID, recordID, creator, createTime, modifier, modifyTime, parentDir, name) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/records/' + recordID + '/';
|
||||
const data = {
|
||||
'creator': creator,
|
||||
'create_time': createTime,
|
||||
'modifier': modifier,
|
||||
'modify_time': modifyTime,
|
||||
'current_dir': parentDir,
|
||||
'name': name,
|
||||
};
|
||||
return this.req.put(url, data);
|
||||
}
|
||||
|
||||
deleteMetadataRecord(repoID, recordID) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/records/' + recordID + '/';
|
||||
return this.req.delete(url);
|
||||
}
|
||||
}
|
||||
|
||||
const metadataManagerAPI = new MetadataManagerAPI();
|
||||
const xcsrfHeaders = cookie.load('sfcsrftoken');
|
||||
metadataManagerAPI.initForSeahubUsage({ siteRoot, xcsrfHeaders });
|
||||
|
||||
export default metadataManagerAPI;
|
@@ -1,11 +0,0 @@
|
||||
.metadata-status-management-dialog .change-metadata-status-management {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.metadata-status-management-dialog .change-metadata-status-management .custom-switch {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.metadata-status-management-dialog .tip {
|
||||
font-size: 12px;
|
||||
}
|
@@ -1,71 +0,0 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import Switch from '../../common/switch';
|
||||
import metadataManagerAPI from '../api';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import toaster from '../../toast';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const MetadataStatusManagementDialog = ({ value: oldValue, repoID, toggle, submit }) => {
|
||||
const [value, setValue] = useState(oldValue);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const onToggle = useCallback(() => {
|
||||
if (submitting) return;
|
||||
toggle && toggle();
|
||||
}, [submitting, toggle]);
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
setSubmitting(true);
|
||||
const apiName = value ? 'openRepoMetadataManagement' : 'closeRepoMetadataManagement';
|
||||
metadataManagerAPI[apiName](repoID).then(res => {
|
||||
submit(value);
|
||||
toggle();
|
||||
}).catch(error => {
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
setSubmitting(false);
|
||||
});
|
||||
}, [repoID, value, submit, toggle]);
|
||||
|
||||
const onValueChange = useCallback(() => {
|
||||
const nextValue = !value;
|
||||
setValue(nextValue);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<Modal className="metadata-status-management-dialog" isOpen={true} toggle={onToggle}>
|
||||
<ModalHeader toggle={onToggle}>{gettext('Extended properties management')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<Switch
|
||||
checked={value}
|
||||
disabled={submitting}
|
||||
size="large"
|
||||
textPosition="right"
|
||||
className="change-metadata-status-management w-100"
|
||||
onChange={onValueChange}
|
||||
placeholder={gettext('Enable extended properties' )}
|
||||
/>
|
||||
<div className="tip">
|
||||
{gettext('After enable extended properties for files, you can add different properties to files, like collaborators, file expiring time, file description. You can also create different views for files based extended properties.')}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={onToggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" disabled={oldValue === value || submitting} onClick={onSubmit}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
MetadataStatusManagementDialog.propTypes = {
|
||||
value: PropTypes.bool,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
submit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default MetadataStatusManagementDialog;
|
@@ -1,28 +0,0 @@
|
||||
.metadata-tree-view .tree-node-inner {
|
||||
height: 28px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.metadata-tree-view .tree-node-inner .left-icon {
|
||||
top: 6px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.metadata-tree-view .tree-node-inner .tree-node-text {
|
||||
padding-left: 28.8px;
|
||||
}
|
||||
|
||||
.metadata-tree-view .tree-node-icon {
|
||||
height: 100%;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.metadata-tree-view .metadata-views-icon {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { siteRoot } from '../../../utils/constants';
|
||||
import Icon from '../../icon';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const MetadataViews = ({ repoID }) => {
|
||||
const [highlight, setHighlight] = useState(false);
|
||||
|
||||
const onMouseEnter = useCallback(() => {
|
||||
setHighlight(true);
|
||||
}, []);
|
||||
|
||||
const onMouseOver = useCallback(() => {
|
||||
setHighlight(true);
|
||||
}, []);
|
||||
|
||||
const onMouseLeave = useCallback(() => {
|
||||
setHighlight(false);
|
||||
}, []);
|
||||
|
||||
const openView = useCallback(() => {
|
||||
const server = siteRoot.substring(0, siteRoot.length-1);
|
||||
window.open(server + '/repos/' + repoID + '/metadata/table-view/', '_blank');
|
||||
}, [repoID]);
|
||||
|
||||
return (
|
||||
<div className="tree-view tree metadata-tree-view">
|
||||
<div className="tree-node">
|
||||
<div className="children" style={{ paddingLeft: 20 }}>
|
||||
<div
|
||||
className={`tree-node-inner text-nowrap${highlight ? ' tree-node-inner-hover' : ''}`}
|
||||
title={gettext('File extended properties')}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseOver={onMouseOver}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onClick={openView}
|
||||
>
|
||||
<div className="tree-node-text">{gettext('File extended properties')}</div>
|
||||
<div className="left-icon">
|
||||
<div className="tree-node-icon">
|
||||
<Icon symbol="table" className="metadata-views-icon" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
MetadataViews.propTypes = {
|
||||
repoID: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default MetadataViews;
|
Reference in New Issue
Block a user