1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-04-28 03:10:45 +00:00

remove useless

This commit is contained in:
Michael An 2025-04-02 11:35:50 +08:00
parent d1f10dbf60
commit 3a3e5fb4f0
17 changed files with 3 additions and 1323 deletions

View File

@ -33,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,
};

View File

@ -1,126 +0,0 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Button, ModalBody, ModalFooter, Input, Label } 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>{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>{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

@ -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

@ -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

@ -15,7 +15,6 @@ import MoveDirentDialog from '../dialog/move-dirent-dialog';
import CopyDirentDialog from '../dialog/copy-dirent-dialog';
import ShareDialog from '../dialog/share-dialog';
import ZipDownloadDialog from '../dialog/zip-download-dialog';
import EditFileTagDialog from '../dialog/edit-filetag-dialog';
import Rename from '../../components/dialog/rename-dirent';
import CreateFile from '../dialog/create-file-dialog';
import CreateFolder from '../dialog/create-folder-dialog';
@ -53,7 +52,6 @@ const propTypes = {
updateDirent: PropTypes.func.isRequired,
onGridItemClick: PropTypes.func,
repoTags: PropTypes.array.isRequired,
onFileTagChanged: PropTypes.func,
onAddFolder: PropTypes.func.isRequired,
showDirentDetail: PropTypes.func.isRequired,
onItemRename: PropTypes.func.isRequired,
@ -80,7 +78,6 @@ class DirentGridView extends React.Component {
isShareDialogShow: false,
isMoveDialogShow: false,
isCopyDialogShow: false,
isEditFileTagShow: false,
isZipDialogOpen: false,
isRenameDialogShow: false,
isCreateFolderDialogShow: false,
@ -376,9 +373,6 @@ class DirentGridView extends React.Component {
case 'Convert to sdoc':
this.onItemConvert(currentObject, event, 'sdoc');
break;
case 'Tags':
this.onEditFileTagToggle();
break;
case 'Permission':
this.onPermissionItem();
break;
@ -453,18 +447,6 @@ class DirentGridView extends React.Component {
hideMenu();
};
onEditFileTagToggle = () => {
this.setState({
isEditFileTagShow: !this.state.isEditFileTagShow
});
};
onFileTagChanged = () => {
let dirent = this.state.activeDirent ? this.state.activeDirent : '';
let direntPath = Utils.joinPath(this.props.path, dirent.name);
this.props.onFileTagChanged(dirent, direntPath);
};
getDirentPath = (dirent) => {
let path = this.props.path;
return path === '/' ? path + dirent.name : path + '/' + dirent.name;
@ -1004,16 +986,6 @@ class DirentGridView extends React.Component {
onAddFolder={this.props.onAddFolder}
/>
}
{this.state.isEditFileTagShow &&
<EditFileTagDialog
repoID={this.props.repoID}
fileTagList={dirent.file_tags}
filePath={direntPath}
toggleCancel={this.onEditFileTagToggle}
repoTags={this.props.repoTags}
onFileTagChanged={this.onFileTagChanged}
/>
}
{this.state.isShareDialogShow &&
<ModalPortal>
<ShareDialog

View File

@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import MediaQuery from 'react-responsive';
import dayjs from 'dayjs';
import { DropdownItem } from 'reactstrap';
import { gettext, siteRoot, mediaUrl, username, useGoFileserver, fileServerRoot, enableVideoThumbnail, enablePDFThumbnail } from '../../utils/constants';
@ -16,8 +15,6 @@ import MoveDirentDialog from '../dialog/move-dirent-dialog';
import CopyDirentDialog from '../dialog/copy-dirent-dialog';
import ShareDialog from '../dialog/share-dialog';
import ZipDownloadDialog from '../dialog/zip-download-dialog';
import EditFileTagDialog from '../dialog/edit-filetag-dialog';
import EditFileTagPopover from '../popover/edit-filetag-popover';
import LibSubFolderPermissionDialog from '../dialog/lib-sub-folder-permission-dialog';
import FileAccessLog from '../dialog/file-access-log';
import toaster from '../toast';

View File

@ -1,31 +0,0 @@
.list-tag-popover .popover {
width: 500px;
max-width: 500px;
}
.list-tag-popover .add-tag-link {
cursor: pointer;
}
.list-tag-popover .tag-list-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
padding: 1rem;
border-top: 1px solid #dedede;
}
.list-tag-popover .tag-list-footer .item-text {
color: #ff9800;
cursor: pointer;
}
.list-tag-popover .tag-list-footer a:hover {
text-decoration: none;
}
.list-tag-popover .tag-color {
width: 20px;
height: 20px;
}

View File

@ -1,160 +0,0 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { v4 as uuidv4 } from 'uuid';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import toaster from '../toast';
import RepoTag from '../../models/repo-tag';
import TagListItem from './tag-list-item';
import VirtualTagListItem from './virtual-tag-list-item';
import TagListFooter from './tag-list-footer';
import { TAG_COLORS } from '../../constants/';
import '../../css/repo-tag.css';
import './list-tag-popover.css';
export default class ListTagPopover extends React.Component {
static propTypes = {
repoID: PropTypes.string.isRequired,
onListTagCancel: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
repotagList: []
};
}
componentDidMount() {
this.loadTags();
}
componentWillUnmount() {
this.setState = () => {};
}
loadTags = () => {
seafileAPI.listRepoTags(this.props.repoID).then(res => {
let repotagList = [];
res.data.repo_tags.forEach(item => {
let repo_tag = new RepoTag(item);
repotagList.push(repo_tag);
});
this.setState({ repotagList });
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
updateTags = (newRepotagList) => {
this.setState({
repotagList: [...this.state.repotagList, ...newRepotagList],
});
};
onDeleteTag = (tag) => {
const { repoID } = this.props;
const { id: targetTagID } = tag;
seafileAPI.deleteRepoTag(repoID, targetTagID).then((res) => {
this.setState({
repotagList: this.state.repotagList.filter(tag => tag.id != targetTagID)
});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
createVirtualTag = (e) => {
e.preventDefault();
let { repotagList } = this.state;
let virtual_repo_tag = {
name: '',
color: TAG_COLORS[Math.floor(Math.random() * TAG_COLORS.length)], // generate random tag color for virtual tag
id: `virtual-tag-${uuidv4()}`,
is_virtual: true,
};
repotagList.push(virtual_repo_tag);
this.setState({ repotagList });
};
deleteVirtualTag = (virtualTag) => {
let { repotagList } = this.state;
let index = repotagList.findIndex(item => item.id === virtualTag.id);
repotagList.splice(index, 1);
this.setState({ repotagList });
};
updateVirtualTag = (virtualTag, data) => {
const repoID = this.props.repoID;
const { repotagList } = this.state;
const index = repotagList.findIndex(item => item.id === virtualTag.id);
if (index < 0) return null;
// If virtual tag color is updated and virtual tag name is empty, it will be saved to local state, don't save it to the server
if (data.color) {
virtualTag.color = data.color;
repotagList[index] = virtualTag;
this.setState({ repotagList });
return;
}
// If virtual tag name is updated and name is not empty, virtual tag color use default, save it to the server
if (data.name && data.name.length > 0) {
let color = virtualTag.color;
let name = data.name;
seafileAPI.createRepoTag(repoID, name, color).then((res) => {
// After saving sag to the server, replace the virtual tag with newly created tag
repotagList[index] = new RepoTag(res.data.repo_tag);
this.setState({ repotagList });
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
};
render() {
return (
<Fragment>
<ul className="tag-list tag-list-container my-2">
{this.state.repotagList.map((repoTag, index) => {
if (repoTag.is_virtual) {
return (
<VirtualTagListItem
key={index}
item={repoTag}
repoID={this.props.repoID}
deleteVirtualTag={this.deleteVirtualTag}
updateVirtualTag={this.updateVirtualTag}
/>
);
} else {
return (
<TagListItem
key={index}
item={repoTag}
repoID={this.props.repoID}
onDeleteTag={this.onDeleteTag}
/>
);
}
})}
</ul>
<div className="add-tag-link px-4 py-2 d-flex align-items-center" onClick={this.createVirtualTag}>
<span className="sf2-icon-plus mr-2"></span>{gettext('Create a new tag')}
</div>
<TagListFooter
toggle={this.props.onListTagCancel}
repotagList={this.state.repotagList}
updateTags={this.updateTags}
repoID={this.props.repoID}
/>
</Fragment>
);
}
}

View File

@ -1,137 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Tooltip } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import RepoTag from '../../models/repo-tag';
import toaster from '../toast';
export default class TagListFooter extends Component {
static propTypes = {
repoID: PropTypes.string.isRequired,
toggle: PropTypes.func.isRequired,
repotagList: PropTypes.array.isRequired,
updateTags: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
showTooltip: false,
};
}
toggleTooltip = () => {
this.setState({ showTooltip: !this.state.showTooltip });
};
onClickImport = () => {
this.importOptionsInput.click();
};
importTagsInputChange = () => {
if (!this.importOptionsInput.files || !this.importOptionsInput.files.length) {
toaster.warning(gettext('Please select a file'));
return;
}
const fileReader = new FileReader();
fileReader.onload = this.onImportTags.bind(this);
fileReader.onerror = this.onImportTagsError.bind(this);
fileReader.readAsText(this.importOptionsInput.files[0]);
};
getValidTags = (tags) => {
let validTags = [];
let tagNameMap = {};
this.props.repotagList.forEach(tag => tagNameMap[tag.name] = true);
for (let i = 0; i < tags.length; i++) {
if (!tags[i] || typeof tags[i] !== 'object' || !tags[i].name || !tags[i].color) {
continue;
}
if (!tagNameMap[tags[i].name]) {
validTags.push(
{
name: tags[i].name,
color: tags[i].color,
}
);
tagNameMap[tags[i].name] = true;
}
}
return validTags;
};
onImportTags = (event) => {
let tags = [];
try {
tags = JSON.parse(event.target.result); // handle JSON file format is error
} catch (error) {
toaster.danger(gettext('The imported tags are invalid'));
return;
}
if (!Array.isArray(tags) || tags.length === 0) {
toaster.danger(gettext('The imported tags are invalid'));
return;
}
let validTags = this.getValidTags(tags);
if (validTags.length === 0) {
toaster.warning(gettext('The imported tag already exists'));
return;
}
seafileAPI.createRepoTags(this.props.repoID, validTags).then((res) => {
toaster.success(gettext('Tags imported'));
let repotagList = [];
res.data.repo_tags.forEach(item => {
let repo_tag = new RepoTag(item);
repotagList.push(repo_tag);
});
this.props.updateTags(repotagList);
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
this.importOptionsInput.value = null;
};
onImportTagsError = () => {
toaster.success(gettext('Failed to import tags. Please reupload.'));
};
getDownloadUrl = () => {
const tags = this.props.repotagList.map(item => {
return { name: item.name, color: item.color };
});
return `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(tags))}`;
};
render() {
return (
<div className="tag-list-footer">
<span className="sf3-font sf3-font-tips mr-2" style={{ color: '#999' }} id="import-export-tags-tip"></span>
<Tooltip
toggle={this.toggleTooltip}
delay={{ show: 0, hide: 0 }}
target='import-export-tags-tip'
placement='bottom'
isOpen={this.state.showTooltip}
>
{gettext('Use the import/export function to transfer tags quickly to another library. (The export is in JSON format.)')}
</Tooltip>
<input
type="file"
ref={ref => this.importOptionsInput = ref}
accept='.json'
className="d-none"
onChange={this.importTagsInputChange}
/>
<span className="item-text" onClick={this.onClickImport}>{gettext('Import tags')}</span>
<span className="mx-2">|</span>
<a href={this.getDownloadUrl()} download='tags.json' onClick={this.props.toggle}>
<span className="item-text">{gettext('Export tags')}</span>
</a>
</div>
);
}
}

View File

@ -1,66 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import TagColor from '../dialog/tag-color';
import TagName from '../dialog/tag-name';
import '../../css/repo-tag.css';
import './list-tag-popover.css';
const tagListItemPropTypes = {
item: PropTypes.object.isRequired,
repoID: PropTypes.string.isRequired,
onDeleteTag: PropTypes.func.isRequired
};
class TagListItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isTagHighlighted: false
};
}
onMouseOver = () => {
this.setState({
isTagHighlighted: true
});
};
onMouseOut = () => {
this.setState({
isTagHighlighted: false
});
};
deleteTag = () => {
this.props.onDeleteTag(this.props.item);
};
render() {
const { isTagHighlighted } = this.state;
const { item, repoID } = this.props;
return (
<li
className={`tag-list-item px-4 d-flex justify-content-between align-items-center ${isTagHighlighted ? 'hl' : ''}`}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
>
<TagColor repoID={repoID} tag={item} />
<TagName repoID={repoID} tag={item} />
<button
className={`tag-delete-icon sf3-font-delete1 sf3-font border-0 px-0 bg-transparent cursor-pointer ${isTagHighlighted ? '' : 'invisible'}`}
onClick={this.deleteTag}
aria-label={gettext('Delete')}
title={gettext('Delete')}
>
</button>
</li>
);
}
}
TagListItem.propTypes = tagListItemPropTypes;
export default TagListItem;

View File

@ -1,96 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Popover, PopoverBody } from 'reactstrap';
import { TAG_COLORS } from '../../constants';
import '../../css/repo-tag.css';
export default class VirtualTagColor extends React.Component {
static propTypes = {
updateVirtualTag: PropTypes.func.isRequired,
tag: PropTypes.object.isRequired,
repoID: PropTypes.string.isRequired
};
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;
this.props.updateVirtualTag(this.props.tag, { color: newColor });
this.setState({
tagColor: newColor,
isPopoverOpen: !this.state.isPopoverOpen,
});
};
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>
);
}
}

View File

@ -1,59 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import VirtualTagColor from './virtual-tag-color';
import VirtualTagName from './virtual-tag-name';
import '../../css/repo-tag.css';
import './list-tag-popover.css';
export default class VirtualTagListItem extends React.Component {
static propTypes = {
item: PropTypes.object.isRequired,
repoID: PropTypes.string.isRequired,
deleteVirtualTag: PropTypes.func.isRequired,
updateVirtualTag: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
isTagHighlighted: false
};
}
onMouseOver = () => {
this.setState({ isTagHighlighted: true });
};
onMouseOut = () => {
this.setState({ isTagHighlighted: false });
};
deleteVirtualTag = () => {
this.props.deleteVirtualTag(this.props.item);
};
render() {
const { isTagHighlighted } = this.state;
const { item, repoID } = this.props;
return (
<li
className={`tag-list-item px-4 d-flex justify-content-between align-items-center ${isTagHighlighted ? 'hl' : ''}`}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
>
<VirtualTagColor repoID={repoID} tag={item} updateVirtualTag={this.props.updateVirtualTag} />
<VirtualTagName repoID={repoID} tag={item} updateVirtualTag={this.props.updateVirtualTag} />
<button
className={`tag-delete-icon sf3-font-delete1 sf3-font border-0 px-0 bg-transparent cursor-pointer ${isTagHighlighted ? '' : 'invisible'}`}
onClick={this.deleteVirtualTag}
aria-label={gettext('Delete')}
title={gettext('Delete')}
>
</button>
</li>
);
}
}

View File

@ -1,90 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import '../../css/repo-tag.css';
export default class VirtualTagName extends React.Component {
static propTypes = {
updateVirtualTag: PropTypes.func.isRequired,
tag: PropTypes.object.isRequired,
repoID: PropTypes.string.isRequired
};
constructor(props) {
super(props);
this.state = {
tagName: this.props.tag.name,
isEditing: true,
};
this.input = React.createRef();
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.tag.name !== this.props.tag.name) {
this.setState({
tagName: nextProps.tag.name,
});
}
}
componentDidMount() {
setTimeout(() => {
this.input.current.focus();
}, 1);
}
toggleMode = () => {
this.setState({
isEditing: !this.state.isEditing
});
};
updateTagName = (e) => {
const newName = e.target.value;
this.props.updateVirtualTag(this.props.tag, { name: newName });
this.setState({
tagName: newName
});
};
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"
style={{ width: 100, height: 20 }}
>{tagName}
</span>
}
</div>
);
}
}

View File

@ -6,7 +6,6 @@ import { seafileAPI } from '../../utils/seafile-api';
import URLDecorator from '../../utils/url-decorator';
import MoveDirentDialog from '../dialog/move-dirent-dialog';
import CopyDirentDialog from '../dialog/copy-dirent-dialog';
import EditFileTagDialog from '../dialog/edit-filetag-dialog';
import ZipDownloadDialog from '../dialog/zip-download-dialog';
import ShareDialog from '../dialog/share-dialog';
import Rename from '../dialog/rename-dirent';
@ -23,7 +22,6 @@ const propTypes = {
userPerm: PropTypes.string.isRequired,
repoID: PropTypes.string.isRequired,
repoEncrypted: PropTypes.bool.isRequired,
repoTags: PropTypes.array.isRequired,
selectedDirentList: PropTypes.array.isRequired,
onItemsMove: PropTypes.func.isRequired,
onItemsCopy: PropTypes.func.isRequired,
@ -54,9 +52,7 @@ class SelectedDirentsToolbar extends React.Component {
isMultipleOperation: true,
showLibContentViewDialogs: false,
showShareDialog: false,
showEditFileTagDialog: false,
fileTagList: [],
multiFileTagList: [],
isRenameDialogOpen: false,
isPermissionDialogOpen: false
};
@ -174,9 +170,6 @@ class SelectedDirentsToolbar extends React.Component {
case 'Permission':
this.onPermissionItem();
break;
case 'Tags':
this.listFileTags(dirent);
break;
case 'Lock':
this.lockFile(dirent);
break;
@ -297,46 +290,17 @@ class SelectedDirentsToolbar extends React.Component {
this.setState({
showLibContentViewDialogs: false,
showShareDialog: false,
showEditFileTagDialog: false,
isRenameDialogOpen: false,
isPermissionDialogOpen: false,
});
};
listFileTags = (dirent) => {
let filePath = this.getDirentPath(dirent);
seafileAPI.listFileTags(this.props.repoID, filePath).then(res => {
let fileTagList = res.data.file_tags;
for (let i = 0, length = fileTagList.length; i < length; i++) {
fileTagList[i].id = fileTagList[i].file_tag_id;
}
this.setState({
fileTagList: fileTagList,
showLibContentViewDialogs: true,
showEditFileTagDialog: true,
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
onMenuFileTagChanged = () => {
this.listFileTags(this.props.selectedDirentList[0]);
let length = this.props.selectedDirentList.length;
for (let i = 0; i < length; i++) {
const dirent = this.props.selectedDirentList[i];
const direntPath = this.getDirentPath(dirent);
this.props.onFilesTagChanged(dirent, direntPath);
}
};
getDirentPath = (dirent) => {
if (dirent) return Utils.joinPath(this.props.path, dirent.name);
};
render() {
const { repoID, repoTags, userPerm, selectedDirentList } = this.props;
const { repoID, userPerm, selectedDirentList } = this.props;
const dirent = selectedDirentList[0];
const selectedLen = selectedDirentList.length;
const direntPath = this.getDirentPath(dirent);
@ -480,18 +444,6 @@ class SelectedDirentsToolbar extends React.Component {
/>
</ModalPortal>
}
{this.state.showEditFileTagDialog &&
<ModalPortal>
<EditFileTagDialog
repoID={repoID}
repoTags={repoTags}
filePath={direntPath}
fileTagList={this.state.fileTagList}
toggleCancel={this.toggleCancel}
onFileTagChanged={this.onMenuFileTagChanged}
/>
</ModalPortal>
}
{this.state.isFileAccessLogDialogOpen &&
<ModalPortal>
<FileAccessLog

View File

@ -5,7 +5,6 @@ import { gettext, siteRoot } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import ModalPortal from '../modal-portal';
import ShareDialog from '../dialog/share-dialog';
import EditFileTagDialog from '../dialog/edit-filetag-dialog';
const propTypes = {
path: PropTypes.string.isRequired,
@ -31,7 +30,6 @@ class ViewFileToolbar extends React.Component {
isDropdownMenuOpen: false,
isMoreMenuShow: false,
isShareDialogShow: false,
isEditTagDialogShow: false,
};
}
@ -90,10 +88,6 @@ class ViewFileToolbar extends React.Component {
this.setState({ isShareDialogShow: !this.state.isShareDialogShow });
};
onEditFileTagToggle = () => {
this.setState({ isEditTagDialogShow: !this.state.isEditTagDialogShow });
};
onHistoryClick = () => {
let historyUrl = siteRoot + 'repo/file_revisions/' + this.props.repoID + '/?p=' + Utils.encodePath(this.props.path);
location.href = historyUrl;
@ -111,25 +105,6 @@ class ViewFileToolbar extends React.Component {
});
}
if (filePermission === 'rw') {
/*
let newSubOpList = [];
if (showShareBtn) {
newSubOpList.push({
'text': gettext('Share'),
'onClick': this.onShareToggle
});
}
newSubOpList.push(
{'text': gettext('Tags'), 'onClick': this.onEditFileTagToggle},
{'text': gettext('History'), 'onClick': this.onHistoryClick}
);
opList.push({
'icon': 'more-vertical',
'text': gettext('More'),
'subOpList': newSubOpList
});
*/
if (showShareBtn) {
opList.push({
'icon': 'share',
@ -137,9 +112,7 @@ class ViewFileToolbar extends React.Component {
'onClick': this.onShareToggle
});
}
opList.push(
{ 'icon': 'tag', 'text': gettext('Tags'), 'onClick': this.onEditFileTagToggle },
{ 'icon': 'history', 'text': gettext('History'), 'onClick': this.onHistoryClick }
);
}
@ -220,18 +193,6 @@ class ViewFileToolbar extends React.Component {
/>
</ModalPortal>
)}
{this.state.isEditTagDialogShow && (
<ModalPortal>
<EditFileTagDialog
filePath={this.props.path}
repoID={this.props.repoID}
repoTags={this.props.repoTags}
fileTagList={this.props.fileTags}
toggleCancel={this.onEditFileTagToggle}
onFileTagChanged={this.props.onFileTagChanged}
/>
</ModalPortal>
)}
</Fragment>
);
}

View File

@ -1,8 +1,7 @@
import React, { Fragment } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import MD5 from 'MD5';
import { createRoot } from 'react-dom/client';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem, UncontrolledTooltip } from 'reactstrap';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import Account from './components/common/account';
@ -1113,14 +1112,6 @@ class Item extends React.Component {
render() {
const { item, isDesktop, mode } = this.props;
const { isIconShown } = this.state;
let toolTipID = '';
let tagTitle = '';
if (item.file_tags && item.file_tags.length > 0) {
toolTipID = MD5(item.file_name).slice(0, 7);
tagTitle = item.file_tags.map(item => item.tag_name).join(' ');
}
if (item.is_dir) {
return isDesktop ? (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>