mirror of
https://github.com/haiwen/seahub.git
synced 2025-04-28 03:10:45 +00:00
migrate tags UI and delete old tags (#7699)
* basic codes remove useless * remove useless * remove useless codes * remove useless codes
This commit is contained in:
parent
894679436c
commit
56d4ebc785
@ -10,4 +10,8 @@ export const EVENT_BUS_TYPE = {
|
||||
|
||||
RESTORE_IMAGE: 'restore_image',
|
||||
OPEN_MARKDOWN: 'open_markdown',
|
||||
|
||||
// migrate tags
|
||||
OPEN_TREE_PANEL: 'open_tree_panel',
|
||||
OPEN_LIBRARY_SETTINGS_TAGS: 'open_library_settings_tags',
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -1,11 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||
import { gettext, enableFileTags } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import TextTranslation from '../../utils/text-translation';
|
||||
import CustomizePopover from '../customize-popover';
|
||||
import ListTagPopover from '../popover/list-tag-popover';
|
||||
import ViewModes from '../../components/view-modes';
|
||||
import SortMenu from '../../components/sort-menu';
|
||||
import MetadataViewToolBar from '../../metadata/components/view-toolbar';
|
||||
@ -16,11 +11,8 @@ import AllTagsSortSetter from '../../tag/views/all-tags/tags-table/sort-setter';
|
||||
import TagFilesSortSetter from '../../tag/views/tag-files/sort-setter';
|
||||
|
||||
const propTypes = {
|
||||
repoID: PropTypes.string.isRequired,
|
||||
userPerm: PropTypes.string,
|
||||
currentPath: PropTypes.string.isRequired,
|
||||
updateUsedRepoTags: PropTypes.func.isRequired,
|
||||
onDeleteRepoTag: PropTypes.func.isRequired,
|
||||
currentMode: PropTypes.string.isRequired,
|
||||
switchViewMode: PropTypes.func.isRequired,
|
||||
isCustomPermission: PropTypes.bool,
|
||||
@ -34,72 +26,13 @@ const propTypes = {
|
||||
|
||||
class DirTool extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isRepoTagDialogOpen: false,
|
||||
isDropdownMenuOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
toggleDropdownMenu = () => {
|
||||
this.setState({
|
||||
isDropdownMenuOpen: !this.state.isDropdownMenuOpen
|
||||
});
|
||||
};
|
||||
|
||||
hidePopover = (e) => {
|
||||
if (e) {
|
||||
let dom = e.target;
|
||||
while (dom) {
|
||||
if (typeof dom.className === 'string' && dom.className.includes('tag-color-popover')) return;
|
||||
dom = dom.parentNode;
|
||||
}
|
||||
}
|
||||
this.setState({ isRepoTagDialogOpen: false });
|
||||
};
|
||||
|
||||
toggleCancel = () => {
|
||||
this.setState({ isRepoTagDialogOpen: false });
|
||||
};
|
||||
|
||||
getMenu = () => {
|
||||
const list = [];
|
||||
const { userPerm, currentPath } = this.props;
|
||||
if (userPerm !== 'rw' || Utils.isMarkdownFile(currentPath)) {
|
||||
return list;
|
||||
}
|
||||
const { TAGS } = TextTranslation;
|
||||
if (enableFileTags) {
|
||||
list.push(TAGS);
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
onMenuItemClick = (item) => {
|
||||
const { key } = item;
|
||||
switch (key) {
|
||||
case 'Tags':
|
||||
this.setState({ isRepoTagDialogOpen: !this.state.isRepoTagDialogOpen });
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
onMenuItemKeyDown = (e, item) => {
|
||||
if (e.key == 'Enter' || e.key == 'Space') {
|
||||
this.onMenuItemClick(item);
|
||||
}
|
||||
};
|
||||
|
||||
onSelectSortOption = (item) => {
|
||||
const [sortBy, sortOrder] = item.value.split('-');
|
||||
this.props.sortItems(sortBy, sortOrder);
|
||||
};
|
||||
|
||||
render() {
|
||||
const menuItems = this.getMenu();
|
||||
const { isDropdownMenuOpen } = this.state;
|
||||
const { repoID, currentMode, currentPath, sortBy, sortOrder, viewId, isCustomPermission, onToggleDetail, onCloseDetail } = this.props;
|
||||
const { currentMode, currentPath, sortBy, sortOrder, viewId, isCustomPermission, onToggleDetail, onCloseDetail } = this.props;
|
||||
const propertiesText = TextTranslation.PROPERTIES.value;
|
||||
const isFileExtended = currentPath.startsWith('/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES + '/');
|
||||
const isTagView = currentPath.startsWith('/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES + '/');
|
||||
@ -128,62 +61,15 @@ class DirTool extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="dir-tool d-flex">
|
||||
<ViewModes currentViewMode={currentMode} switchViewMode={this.props.switchViewMode} />
|
||||
<SortMenu sortBy={sortBy} sortOrder={sortOrder} onSelectSortOption={this.onSelectSortOption} />
|
||||
{(!isCustomPermission) &&
|
||||
<div className="cur-view-path-btn" onClick={onToggleDetail}>
|
||||
<span className="sf3-font sf3-font-info" aria-label={propertiesText} title={propertiesText}></span>
|
||||
</div>
|
||||
}
|
||||
{menuItems.length > 0 &&
|
||||
<Dropdown isOpen={isDropdownMenuOpen} toggle={this.toggleDropdownMenu}>
|
||||
<DropdownToggle
|
||||
tag="i"
|
||||
id="cur-folder-more-op-toggle"
|
||||
className='cur-view-path-btn sf3-font-more sf3-font'
|
||||
data-toggle="dropdown"
|
||||
title={gettext('More operations')}
|
||||
aria-label={gettext('More operations')}
|
||||
aria-expanded={isDropdownMenuOpen}
|
||||
>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu>
|
||||
{menuItems.map((menuItem, index) => {
|
||||
if (menuItem === 'Divider') {
|
||||
return <DropdownItem key={index} divider />;
|
||||
} else {
|
||||
return (
|
||||
<DropdownItem
|
||||
key={index}
|
||||
onClick={this.onMenuItemClick.bind(this, menuItem)}
|
||||
onKeyDown={this.onMenuItemKeyDown.bind(this, menuItem)}
|
||||
>{menuItem.value}
|
||||
</DropdownItem>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
</div>
|
||||
{this.state.isRepoTagDialogOpen &&
|
||||
<CustomizePopover
|
||||
popoverClassName="list-tag-popover"
|
||||
target="cur-folder-more-op-toggle"
|
||||
hidePopover={this.hidePopover}
|
||||
hidePopoverWithEsc={this.hidePopover}
|
||||
boundariesElement={document.body}
|
||||
placement={'bottom-end'}
|
||||
>
|
||||
<ListTagPopover
|
||||
repoID={repoID}
|
||||
onListTagCancel={this.toggleCancel}
|
||||
/>
|
||||
</CustomizePopover>
|
||||
<div className="dir-tool d-flex">
|
||||
<ViewModes currentViewMode={currentMode} switchViewMode={this.props.switchViewMode} />
|
||||
<SortMenu sortBy={sortBy} sortOrder={sortOrder} onSelectSortOption={this.onSelectSortOption} />
|
||||
{(!isCustomPermission) &&
|
||||
<div className="cur-view-path-btn" onClick={onToggleDetail}>
|
||||
<span className="sf3-font sf3-font-info" aria-label={propertiesText} title={propertiesText}></span>
|
||||
</div>
|
||||
}
|
||||
</React.Fragment>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
@ -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;
|
@ -25,7 +25,7 @@ const propTypes = {
|
||||
currentRepoInfo: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab }) => {
|
||||
const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab, showMigrateTip }) => {
|
||||
const [activeTab, setActiveTab] = useState(tab || TAB.HISTORY_SETTING);
|
||||
|
||||
const toggleTab = useCallback((tab) => {
|
||||
@ -201,6 +201,7 @@ const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab }) => {
|
||||
submit={updateEnableTags}
|
||||
toggleDialog={toggleDialog}
|
||||
enableMetadata={enableMetadata}
|
||||
showMigrateTip={showMigrateTip}
|
||||
/>
|
||||
</TabPane>
|
||||
)}
|
||||
|
@ -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;
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
@ -6,16 +6,34 @@ import TreeSection from '../../tree-section';
|
||||
import TrashDialog from '../../dialog/trash-dialog';
|
||||
import LibSettingsDialog from '../../dialog/lib-settings';
|
||||
import RepoHistoryDialog from '../../dialog/repo-history';
|
||||
import { eventBus } from '../../common/event-bus';
|
||||
import { EVENT_BUS_TYPE } from '../../common/event-bus-type';
|
||||
import { TAB } from '../../../constants/repo-setting-tabs';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const DirOthers = ({ userPerm, repoID, currentRepoInfo }) => {
|
||||
|
||||
const showSettings = currentRepoInfo.is_admin; // repo owner, department admin, shared with 'Admin' permission
|
||||
let [isSettingsDialogOpen, setSettingsDialogOpen] = useState(false);
|
||||
let [activeTab, setActiveTab] = useState(TAB.HISTORY_SETTING);
|
||||
let [showMigrateTip, setShowMigrateTip] = useState(false);
|
||||
|
||||
const toggleSettingsDialog = () => {
|
||||
setSettingsDialogOpen(!isSettingsDialogOpen);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeUnselectFiles = eventBus.subscribe(EVENT_BUS_TYPE.OPEN_LIBRARY_SETTINGS_TAGS, () => {
|
||||
setSettingsDialogOpen(true);
|
||||
setActiveTab(TAB.TAGS_SETTING);
|
||||
setShowMigrateTip(true);
|
||||
});
|
||||
return () => {
|
||||
unsubscribeUnselectFiles();
|
||||
};
|
||||
});
|
||||
|
||||
const [showTrashDialog, setShowTrashDialog] = useState(false);
|
||||
const toggleTrashDialog = () => {
|
||||
setShowTrashDialog(!showTrashDialog);
|
||||
@ -59,6 +77,8 @@ const DirOthers = ({ userPerm, repoID, currentRepoInfo }) => {
|
||||
repoID={repoID}
|
||||
currentRepoInfo={currentRepoInfo}
|
||||
toggleDialog={toggleSettingsDialog}
|
||||
tab={activeTab}
|
||||
showMigrateTip={showMigrateTip}
|
||||
/>
|
||||
)}
|
||||
{isRepoHistoryDialogOpen && (
|
||||
|
@ -3,11 +3,8 @@ import PropTypes from 'prop-types';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import Icon from '../icon';
|
||||
import { gettext, enableFileTags } from '../../utils/constants';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import EditFileTagPopover from '../popover/edit-filetag-popover';
|
||||
import FileTagList from '../file-tag-list';
|
||||
import ExtraMetadataAttributesDialog from '../dialog/extra-metadata-attributes-dialog';
|
||||
|
||||
const propTypes = {
|
||||
@ -18,7 +15,6 @@ const propTypes = {
|
||||
direntType: PropTypes.string.isRequired,
|
||||
direntDetail: PropTypes.object.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
fileTagList: PropTypes.array.isRequired,
|
||||
onFileTagChanged: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
@ -29,7 +25,6 @@ class DetailListView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isEditFileTagShow: false,
|
||||
isShowMetadataExtraProperties: false,
|
||||
};
|
||||
this.tagListTitleID = `detail-list-view-tags-${uuidv4()}`;
|
||||
@ -55,12 +50,6 @@ class DetailListView extends React.Component {
|
||||
return Utils.joinPath(path, dirent.name);
|
||||
};
|
||||
|
||||
onEditFileTagToggle = () => {
|
||||
this.setState({
|
||||
isEditFileTagShow: !this.state.isEditFileTagShow
|
||||
});
|
||||
};
|
||||
|
||||
onFileTagChanged = () => {
|
||||
let direntPath = this.getDirentPath();
|
||||
this.props.onFileTagChanged(this.props.dirent, direntPath);
|
||||
@ -70,8 +59,8 @@ class DetailListView extends React.Component {
|
||||
this.setState({ isShowMetadataExtraProperties: !this.state.isShowMetadataExtraProperties });
|
||||
};
|
||||
|
||||
renderTags = () => {
|
||||
const { direntType, direntDetail, fileTagList = [] } = this.props;
|
||||
renderInfos = () => {
|
||||
const { direntType, direntDetail } = this.props;
|
||||
const position = this.getFileParent();
|
||||
if (direntType === 'dir') {
|
||||
return (
|
||||
@ -106,15 +95,6 @@ class DetailListView extends React.Component {
|
||||
<tr><th>{gettext('Size')}</th><td>{Utils.bytesToSize(direntDetail.size)}</td></tr>
|
||||
<tr><th>{gettext('Location')}</th><td>{position}</td></tr>
|
||||
<tr><th>{gettext('Last Update')}</th><td>{dayjs(direntDetail.last_modified).fromNow()}</td></tr>
|
||||
<tr className="file-tag-container">
|
||||
<th>{gettext('Tags')}</th>
|
||||
<td>
|
||||
<FileTagList fileTagList={fileTagList} />
|
||||
{enableFileTags &&
|
||||
<span onClick={this.onEditFileTagToggle} id={this.tagListTitleID}><Icon symbol='tag' /></span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
{direntDetail.permission === 'rw' && window.app.pageOptions.enableMetadataManagement && (
|
||||
<tr className="file-extra-attributes">
|
||||
<th colSpan={2}>
|
||||
@ -130,24 +110,11 @@ class DetailListView extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { direntType, direntDetail, fileTagList = [] } = this.props;
|
||||
const { direntType, direntDetail } = this.props;
|
||||
const direntPath = this.getDirentPath();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{this.renderTags()}
|
||||
{this.state.isEditFileTagShow &&
|
||||
<EditFileTagPopover
|
||||
repoID={this.props.repoID}
|
||||
repoTags={this.props.repoTags}
|
||||
filePath={direntPath}
|
||||
fileTagList={fileTagList}
|
||||
toggleCancel={this.onEditFileTagToggle}
|
||||
onFileTagChanged={this.onFileTagChanged}
|
||||
target={this.tagListTitleID}
|
||||
isEditFileTagShow={this.state.isEditFileTagShow}
|
||||
/>
|
||||
}
|
||||
{this.renderInfos()}
|
||||
{this.state.isShowMetadataExtraProperties && (
|
||||
<ExtraMetadataAttributesDialog
|
||||
repoID={this.props.repoID}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import MD5 from 'MD5';
|
||||
import { UncontrolledTooltip } from 'reactstrap';
|
||||
import { gettext, siteRoot, mediaUrl, enableVideoThumbnail, enablePDFThumbnail } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { imageThumbnailCenter, videoThumbnailCenter } from '../../utils/thumbnail-center';
|
||||
@ -297,13 +295,7 @@ class DirentGridItem extends React.Component {
|
||||
|
||||
render() {
|
||||
let { dirent, isGridDropTipShow } = this.state;
|
||||
let { is_freezed, is_locked, lock_owner_name, file_tags, isSelected } = dirent;
|
||||
let toolTipID = '';
|
||||
let tagTitle = '';
|
||||
if (file_tags && file_tags.length > 0) {
|
||||
toolTipID = MD5(dirent.name).slice(0, 7);
|
||||
tagTitle = file_tags.map(item => item.name).join(' ');
|
||||
}
|
||||
let { is_freezed, is_locked, lock_owner_name, isSelected } = dirent;
|
||||
const showName = this.getRenderedText(dirent);
|
||||
return (
|
||||
<>
|
||||
@ -341,21 +333,6 @@ class DirentGridItem extends React.Component {
|
||||
}
|
||||
</div>
|
||||
<div className="grid-file-name" onDragStart={this.onGridItemDragStart} draggable={this.canDrag} >
|
||||
{(dirent.type !== 'dir' && file_tags && file_tags.length > 0) && (
|
||||
<>
|
||||
<div id={`tag-list-title-${toolTipID}`} className="dirent-item tag-list tag-list-stacked d-inline-block align-middle">
|
||||
{file_tags.map((fileTag, index) => {
|
||||
let length = file_tags.length;
|
||||
return (
|
||||
<span className="file-tag" key={fileTag.id} style={{ zIndex: length - index, backgroundColor: fileTag.color }}></span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<UncontrolledTooltip target={`tag-list-title-${toolTipID}`} placement="bottom">
|
||||
{tagTitle}
|
||||
</UncontrolledTooltip>
|
||||
</>
|
||||
)}
|
||||
{(!dirent.isDir() && !this.canPreview) ?
|
||||
<a
|
||||
className="sf-link grid-file-name-link"
|
||||
|
@ -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
|
||||
|
@ -1,8 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import MediaQuery from 'react-responsive';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import dayjs from 'dayjs';
|
||||
import { DropdownItem } from 'reactstrap';
|
||||
import { gettext, siteRoot, mediaUrl, username, useGoFileserver, fileServerRoot, enableVideoThumbnail, enablePDFThumbnail } from '../../utils/constants';
|
||||
@ -17,13 +15,10 @@ 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';
|
||||
import MobileItemMenu from '../../components/mobile-item-menu';
|
||||
import FileTag from './file-tag';
|
||||
|
||||
import '../../css/dirent-list-item.css';
|
||||
|
||||
@ -95,10 +90,8 @@ class DirentListItem extends React.Component {
|
||||
isShowTagTooltip: false,
|
||||
isDragTipShow: false,
|
||||
isDropTipshow: false,
|
||||
isEditFileTagShow: false,
|
||||
isPermissionDialogOpen: false
|
||||
};
|
||||
this.tagListTitleID = `tag-list-title-${uuidv4()}`;
|
||||
this.isGeneratingThumbnail = false;
|
||||
this.thumbnailCenter = null;
|
||||
this.dragIconRef = null;
|
||||
@ -350,9 +343,6 @@ class DirentListItem extends React.Component {
|
||||
case 'Copy':
|
||||
this.onItemCopyToggle();
|
||||
break;
|
||||
case 'Tags':
|
||||
this.onEditFileTagToggle();
|
||||
break;
|
||||
case 'Permission':
|
||||
this.onPermissionItem();
|
||||
break;
|
||||
@ -410,12 +400,6 @@ class DirentListItem extends React.Component {
|
||||
this.props.onItemConvert(this.state.dirent, dstType);
|
||||
};
|
||||
|
||||
onEditFileTagToggle = () => {
|
||||
this.setState({
|
||||
isEditFileTagShow: !this.state.isEditFileTagShow
|
||||
});
|
||||
};
|
||||
|
||||
onFileTagChanged = () => {
|
||||
let direntPath = this.getDirentPath(this.state.dirent);
|
||||
this.props.onFileTagChanged(this.state.dirent, direntPath);
|
||||
@ -891,18 +875,6 @@ class DirentListItem extends React.Component {
|
||||
)}
|
||||
</td>
|
||||
<td className="tag-list-title">
|
||||
{(dirent.type !== 'dir' && dirent.file_tags && dirent.file_tags.length > 0) && (
|
||||
<div id={this.tagListTitleID} className="dirent-item tag-list tag-list-stacked">
|
||||
{dirent.file_tags.map((fileTag, index) => {
|
||||
return (
|
||||
<FileTag fileTag={fileTag} length={dirent.file_tags.length} key={index} index={index}/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{(dirent.type !== 'dir' && (!dirent.file_tags || dirent.file_tags.length == 0)) &&
|
||||
<div id={this.tagListTitleID} className="dirent-item tag-list tag-list-stacked"></div>
|
||||
}
|
||||
</td>
|
||||
<td className="operation">{this.renderItemOperation()}</td>
|
||||
<td className="file-size">{dirent.size || ''}</td>
|
||||
@ -993,32 +965,6 @@ class DirentListItem extends React.Component {
|
||||
/>
|
||||
</ModalPortal>
|
||||
}
|
||||
<MediaQuery query="(min-width: 768px)">
|
||||
{this.state.isEditFileTagShow &&
|
||||
<EditFileTagPopover
|
||||
repoID={this.props.repoID}
|
||||
repoTags={this.props.repoTags}
|
||||
fileTagList={dirent.file_tags}
|
||||
filePath={direntPath}
|
||||
toggleCancel={this.onEditFileTagToggle}
|
||||
onFileTagChanged={this.onFileTagChanged}
|
||||
target={this.tagListTitleID}
|
||||
isEditFileTagShow={this.state.isEditFileTagShow}
|
||||
/>
|
||||
}
|
||||
</MediaQuery>
|
||||
<MediaQuery query="(max-width: 767.8px)">
|
||||
{this.state.isEditFileTagShow &&
|
||||
<EditFileTagDialog
|
||||
repoID={this.props.repoID}
|
||||
repoTags={this.props.repoTags}
|
||||
fileTagList={dirent.file_tags}
|
||||
filePath={direntPath}
|
||||
toggleCancel={this.onEditFileTagToggle}
|
||||
onFileTagChanged={this.onFileTagChanged}
|
||||
/>
|
||||
}
|
||||
</MediaQuery>
|
||||
{this.state.isZipDialogOpen &&
|
||||
<ModalPortal>
|
||||
<ZipDownloadDialog
|
||||
|
@ -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;
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
36
frontend/src/components/repo-info-bar-migrate.js
Normal file
36
frontend/src/components/repo-info-bar-migrate.js
Normal file
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Button } from 'reactstrap';
|
||||
import { gettext } from '../utils/constants';
|
||||
import { useMetadataStatus } from '../hooks';
|
||||
import { eventBus } from '../components/common/event-bus';
|
||||
import { EVENT_BUS_TYPE } from '../components/common/event-bus-type';
|
||||
|
||||
const RepoInfoBarMigrate = () => {
|
||||
|
||||
const { enableMetadata } = useMetadataStatus();
|
||||
|
||||
const openMigrate = () => {
|
||||
eventBus.dispatch(EVENT_BUS_TYPE.OPEN_TREE_PANEL, () => eventBus.dispatch(EVENT_BUS_TYPE.OPEN_LIBRARY_SETTINGS_TAGS));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="repo-info-bar-migrate mt-2">
|
||||
{enableMetadata ?
|
||||
(
|
||||
<>
|
||||
{gettext('Tips: There are tags of old version. Please migrate tags to new version.')}
|
||||
<Button color="link" size="sm" tag="a" onClick={openMigrate}>{gettext('Migrate')}</Button>
|
||||
</>
|
||||
) :
|
||||
(
|
||||
<>{gettext('Tips: These are tags of old version. The feature is deprecated and can no longer be used.')}</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RepoInfoBarMigrate.propTypes = {
|
||||
};
|
||||
|
||||
export default RepoInfoBarMigrate;
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ModalPortal from './modal-portal';
|
||||
import ListTaggedFilesDialog from './dialog/list-taggedfiles-dialog';
|
||||
import RepoInfoBarMigrate from './repo-info-bar-migrate';
|
||||
|
||||
import '../css/repo-info-bar.css';
|
||||
|
||||
@ -72,6 +73,7 @@ class RepoInfoBar extends React.Component {
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
<RepoInfoBarMigrate />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -3,13 +3,16 @@
|
||||
margin: 10px 16px 0;
|
||||
border: 1px solid #e6e6dd;
|
||||
border-radius: 5px;
|
||||
/* background: #f8f8f8; */
|
||||
}
|
||||
|
||||
.used-tag-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.used-tag-list .used-tag-item:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.used-tag-item {
|
||||
display: inline-block;
|
||||
margin: auto 15px;
|
||||
@ -60,3 +63,16 @@
|
||||
.readme-dialog .modal-body {
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
.repo-info-bar .repo-info-bar-migrate .btn-link {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
padding: 0px 8px;
|
||||
border: 0;
|
||||
color: #EC8000;
|
||||
}
|
||||
|
||||
.repo-info-bar .repo-info-bar-migrate .btn-link:active {
|
||||
color: #EC8000;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ const langOptions = [
|
||||
}
|
||||
];
|
||||
|
||||
const MetadataTagsStatusDialog = ({ value: oldValue, lang: oldLang, repoID, toggleDialog: toggle, submit, enableMetadata }) => {
|
||||
const MetadataTagsStatusDialog = ({ value: oldValue, lang: oldLang, repoID, toggleDialog: toggle, submit, enableMetadata, showMigrateTip }) => {
|
||||
const [value, setValue] = useState(oldValue);
|
||||
const [lang, setLang] = useState(oldLang);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
@ -46,6 +46,10 @@ const MetadataTagsStatusDialog = ({ value: oldValue, lang: oldLang, repoID, togg
|
||||
});
|
||||
}, [lang, repoID, submit, toggle, value]);
|
||||
|
||||
const migrateTag = useCallback(() => {
|
||||
// TODO backend migrate old tags
|
||||
}, []);
|
||||
|
||||
const turnOffConfirmToggle = useCallback(() => {
|
||||
setShowTurnOffConfirmDialog(!showTurnOffConfirmDialog);
|
||||
}, [showTurnOffConfirmDialog]);
|
||||
@ -104,6 +108,12 @@ const MetadataTagsStatusDialog = ({ value: oldValue, lang: oldLang, repoID, togg
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
{showMigrateTip &&
|
||||
<FormGroup className="mt-6">
|
||||
<p>{gettext('This library contains tags of old version. Do you like to migrate the tags to new version?')}</p>
|
||||
<Button color="primary" onClick={migrateTag}>{gettext('Migrate old version tags')}</Button>
|
||||
</FormGroup>
|
||||
}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={onToggle}>{gettext('Cancel')}</Button>
|
||||
@ -126,6 +136,7 @@ MetadataTagsStatusDialog.propTypes = {
|
||||
toggleDialog: PropTypes.func.isRequired,
|
||||
submit: PropTypes.func.isRequired,
|
||||
enableMetadata: PropTypes.bool.isRequired,
|
||||
showMigrateTip: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default MetadataTagsStatusDialog;
|
||||
|
@ -31,6 +31,7 @@ import Detail from '../../components/dirent-detail';
|
||||
import DirColumnView from '../../components/dir-view-mode/dir-column-view';
|
||||
import SelectedDirentsToolbar from '../../components/toolbar/selected-dirents-toolbar';
|
||||
import MetadataPathToolbar from '../../components/toolbar/metadata-path-toolbar';
|
||||
import { eventBus } from '../../components/common/event-bus';
|
||||
|
||||
import '../../css/lib-content-view.css';
|
||||
|
||||
@ -156,6 +157,7 @@ class LibContentView extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
this.unsubscribeEvent = this.props.eventBus.subscribe(EVENT_BUS_TYPE.SEARCH_LIBRARY_CONTENT, this.onSearchedClick);
|
||||
this.unsubscribeOpenTreePanel = eventBus.subscribe(EVENT_BUS_TYPE.OPEN_TREE_PANEL, this.openTreePanel);
|
||||
this.calculatePara(this.props);
|
||||
}
|
||||
|
||||
@ -268,6 +270,7 @@ class LibContentView extends React.Component {
|
||||
componentWillUnmount() {
|
||||
window.onpopstate = this.oldonpopstate;
|
||||
this.unsubscribeEvent();
|
||||
this.unsubscribeOpenTreePanel();
|
||||
this.unsubscribeEventBus && this.unsubscribeEventBus();
|
||||
this.props.eventBus.dispatch(EVENT_BUS_TYPE.CURRENT_LIBRARY_CHANGED, {
|
||||
repoID: '',
|
||||
@ -2142,21 +2145,6 @@ class LibContentView extends React.Component {
|
||||
this.onDirentSelected(dirent);
|
||||
};
|
||||
|
||||
onDeleteRepoTag = (deletedTagID) => {
|
||||
let direntList = this.state.direntList.map(dirent => {
|
||||
if (dirent.file_tags) {
|
||||
let fileTags = dirent.file_tags.filter(item => {
|
||||
return item.repo_tag_id !== deletedTagID;
|
||||
});
|
||||
dirent.file_tags = fileTags;
|
||||
}
|
||||
return dirent;
|
||||
});
|
||||
this.setState({ direntList: direntList });
|
||||
this.updateUsedRepoTags();
|
||||
};
|
||||
|
||||
|
||||
handleSubmit = (e) => {
|
||||
let options = {
|
||||
'share_type': 'personal',
|
||||
@ -2172,6 +2160,17 @@ class LibContentView extends React.Component {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
openTreePanel = (callback) => {
|
||||
if (this.state.isTreePanelShown) {
|
||||
callback();
|
||||
} else {
|
||||
this.toggleTreePanel();
|
||||
setTimeout(() => {
|
||||
callback();
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
toggleTreePanel = () => {
|
||||
this.setState({
|
||||
isTreePanelShown: !this.state.isTreePanelShown
|
||||
@ -2356,8 +2355,6 @@ class LibContentView extends React.Component {
|
||||
repoName={this.state.currentRepoInfo.repo_name}
|
||||
userPerm={userPerm}
|
||||
currentPath={path}
|
||||
updateUsedRepoTags={this.updateUsedRepoTags}
|
||||
onDeleteRepoTag={this.onDeleteRepoTag}
|
||||
currentMode={this.state.currentMode}
|
||||
switchViewMode={this.switchViewMode}
|
||||
isCustomPermission={isCustomPermission}
|
||||
|
@ -1,106 +1,37 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import RepoTag from '../../../models/repo-tag';
|
||||
import toaster from '../../../components/toast';
|
||||
import Icon from '../../../components/icon';
|
||||
import EditFileTagPopover from '../../../components/popover/edit-filetag-popover';
|
||||
import FileTagList from '../../../components/file-tag-list';
|
||||
|
||||
import '../../../css/dirent-detail.css';
|
||||
import '../css/detail-list-view.css';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const { repoID, filePath } = window.app.pageOptions;
|
||||
const { filePath } = window.app.pageOptions;
|
||||
|
||||
const propTypes = {
|
||||
fileInfo: PropTypes.object.isRequired,
|
||||
fileTagList: PropTypes.array.isRequired,
|
||||
onFileTagChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class DetailListView extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
repoTags: [],
|
||||
isEditFileTagShow: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.listRepoTags(repoID).then(res => {
|
||||
let repoTags = [];
|
||||
res.data.repo_tags.forEach(item => {
|
||||
const repoTag = new RepoTag(item);
|
||||
repoTags.push(repoTag);
|
||||
});
|
||||
this.setState({
|
||||
repoTags: repoTags
|
||||
});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
onNewRepoTagAdded = (newTag) => {
|
||||
const repoTag = new RepoTag(newTag);
|
||||
const { repoTags: newTags } = this.state;
|
||||
newTags.push(repoTag);
|
||||
this.setState({
|
||||
repoTags: newTags
|
||||
});
|
||||
};
|
||||
|
||||
onEditFileTagToggle = () => {
|
||||
this.setState({ isEditFileTagShow: !this.state.isEditFileTagShow });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { fileInfo, fileTagList = [] } = this.props;
|
||||
const { repoTags } = this.state;
|
||||
const { fileInfo } = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="dirent-table-container p-2">
|
||||
<table className="table-thead-hidden">
|
||||
<thead>
|
||||
<tr><th width="35%"></th><th width="65%"></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><th>{gettext('Size')}</th><td>{Utils.bytesToSize(fileInfo.size)}</td></tr>
|
||||
<tr><th>{gettext('Location')}</th><td>{filePath}</td></tr>
|
||||
<tr><th>{gettext('Last Update')}</th><td>{dayjs(fileInfo.mtime * 1000).fromNow()}</td></tr>
|
||||
<tr className="file-tag-container">
|
||||
<th>{gettext('Tags')}</th>
|
||||
<td>
|
||||
<FileTagList fileTagList={fileTagList} />
|
||||
<span onClick={this.onEditFileTagToggle} id='file-tag-container-icon'><Icon symbol='tag' /></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{this.state.isEditFileTagShow &&
|
||||
<EditFileTagPopover
|
||||
repoID={repoID}
|
||||
repoTags={repoTags}
|
||||
filePath={filePath}
|
||||
fileTagList={fileTagList}
|
||||
toggleCancel={this.onEditFileTagToggle}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
onNewRepoTagAdded={this.onNewRepoTagAdded}
|
||||
target={'file-tag-container-icon'}
|
||||
isEditFileTagShow={this.state.isEditFileTagShow}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
<div className="dirent-table-container p-2">
|
||||
<table className="table-thead-hidden">
|
||||
<thead>
|
||||
<tr><th width="35%"></th><th width="65%"></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><th>{gettext('Size')}</th><td>{Utils.bytesToSize(fileInfo.size)}</td></tr>
|
||||
<tr><th>{gettext('Location')}</th><td>{filePath}</td></tr>
|
||||
<tr><th>{gettext('Last Update')}</th><td>{dayjs(fileInfo.mtime * 1000).fromNow()}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ class MarkdownEditor extends React.Component {
|
||||
saving: false,
|
||||
isLocked: isLocked,
|
||||
lockedByMe: lockedByMe,
|
||||
fileTagList: [],
|
||||
participants: [],
|
||||
};
|
||||
|
||||
@ -234,7 +233,6 @@ class MarkdownEditor extends React.Component {
|
||||
},
|
||||
});
|
||||
}
|
||||
this.listFileTags();
|
||||
|
||||
this.listFileParticipants();
|
||||
window.showParticipants = true;
|
||||
@ -274,20 +272,6 @@ class MarkdownEditor extends React.Component {
|
||||
return confirmationMessage;
|
||||
};
|
||||
|
||||
listFileTags = () => {
|
||||
seafileAPI.listFileTags(repoID, filePath).then(res => {
|
||||
let fileTagList = res.data.file_tags;
|
||||
for (let i = 0; i < fileTagList.length; i++) {
|
||||
fileTagList[i].id = fileTagList[i].file_tag_id;
|
||||
}
|
||||
this.setState({ fileTagList: fileTagList });
|
||||
});
|
||||
};
|
||||
|
||||
onFileTagChanged = () => {
|
||||
this.listFileTags();
|
||||
};
|
||||
|
||||
listFileParticipants = () => {
|
||||
editorApi.listFileParticipant().then((res) => {
|
||||
this.setState({ participants: res.data.participant_list });
|
||||
@ -399,7 +383,7 @@ class MarkdownEditor extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, markdownContent, fileInfo, fileTagList, isLocked } = this.state;
|
||||
const { loading, markdownContent, fileInfo, isLocked } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@ -433,9 +417,8 @@ class MarkdownEditor extends React.Component {
|
||||
onSave={this.onSaveEditorContent}
|
||||
onContentChanged={this.onContentChanged}
|
||||
mathJaxSource={mediaUrl + 'js/mathjax/tex-svg.js'}
|
||||
// isSupportInsertSeafileImage={true}
|
||||
>
|
||||
<DetailListView fileInfo={fileInfo} fileTagList={fileTagList} onFileTagChanged={this.onFileTagChanged}/>
|
||||
<DetailListView fileInfo={fileInfo} />
|
||||
</SeafileMarkdownEditor>
|
||||
)}
|
||||
{isLocked && (
|
||||
@ -443,7 +426,6 @@ class MarkdownEditor extends React.Component {
|
||||
isFetching={loading}
|
||||
value={markdownContent}
|
||||
mathJaxSource={mediaUrl + 'js/mathjax/tex-svg.js'}
|
||||
// isSupportInsertSeafileImage={true}
|
||||
isShowOutline={true}
|
||||
>
|
||||
</SeafileMarkdownViewer>
|
||||
|
@ -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}>
|
||||
@ -1180,21 +1171,6 @@ class Item extends React.Component {
|
||||
<a href={fileURL} onClick={this.handleFileClick}>{item.file_name}</a>
|
||||
</td>
|
||||
<td className="tag-list-title">
|
||||
{(item.file_tags && item.file_tags.length > 0) && (
|
||||
<Fragment>
|
||||
<div id={`tag-list-title-${toolTipID}`} className="dirent-item tag-list tag-list-stacked">
|
||||
{item.file_tags.map((fileTag, index) => {
|
||||
let length = item.file_tags.length;
|
||||
return (
|
||||
<span className="file-tag" key={fileTag.file_tag_id} style={{ zIndex: length - index, backgroundColor: fileTag.tag_color }}></span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<UncontrolledTooltip target={`tag-list-title-${toolTipID}`} placement="bottom">
|
||||
{tagTitle}
|
||||
</UncontrolledTooltip>
|
||||
</Fragment>
|
||||
)}
|
||||
</td>
|
||||
<td>{Utils.bytesToSize(item.size)}</td>
|
||||
<td title={dayjs(item.last_modified).format('dddd, MMMM D, YYYY h:mm:ss A')}>{dayjs(item.last_modified).fromNow()}</td>
|
||||
|
@ -1363,26 +1363,6 @@ class SeafileAPI {
|
||||
return this._sendPostRequest(url, form);
|
||||
}
|
||||
|
||||
createRepoTags(repoID, tags) {
|
||||
var url = this.server + '/api/v2.1/repos/' + repoID + '/repo-tags/';
|
||||
var params = { tags };
|
||||
return this.req.put(url, params);
|
||||
}
|
||||
|
||||
deleteRepoTag(repoID, repo_tag_id) {
|
||||
var url = this.server + '/api/v2.1/repos/' + repoID + '/repo-tags/' + repo_tag_id + '/';
|
||||
return this.req.delete(url);
|
||||
}
|
||||
|
||||
updateRepoTag(repoID, repo_tag_id, name, color) {
|
||||
var url = this.server + '/api/v2.1/repos/' + repoID + '/repo-tags/' + repo_tag_id + '/';
|
||||
var params = {
|
||||
name: name,
|
||||
color: color,
|
||||
};
|
||||
return this.req.put(url, params);
|
||||
}
|
||||
|
||||
listTaggedFiles(repoID, repoTagId) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/tagged-files/' + repoTagId + '/';
|
||||
return this.req.get(url);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { mediaUrl, gettext, serviceURL, siteRoot, isPro, fileAuditEnabled, canGenerateShareLink, canGenerateUploadLink, shareLinkPasswordMinLength, username, folderPermEnabled, onlyofficeConverterExtensions, enableSeadoc, enableFileTags, enableRepoSnapshotLabel,
|
||||
import { mediaUrl, gettext, serviceURL, siteRoot, isPro, fileAuditEnabled, canGenerateShareLink, canGenerateUploadLink, shareLinkPasswordMinLength, username, folderPermEnabled, onlyofficeConverterExtensions, enableSeadoc, enableRepoSnapshotLabel,
|
||||
enableResetEncryptedRepoPassword, isEmailConfigured, isSystemStaff,
|
||||
enableOnlyoffice, onlyofficeEditFileExtension,
|
||||
enableOfficeWebApp, officeWebAppEditFileExtension } from './constants';
|
||||
@ -555,7 +555,7 @@ export const Utils = {
|
||||
getFileOperationList: function (isRepoOwner, currentRepoInfo, dirent, isContextmenu) {
|
||||
let list = [];
|
||||
const {
|
||||
SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, TAGS, UNLOCK, LOCK, UNFREEZE_DOCUMENT, FREEZE_DOCUMENT,
|
||||
SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, UNLOCK, LOCK, UNFREEZE_DOCUMENT, FREEZE_DOCUMENT,
|
||||
HISTORY, ACCESS_LOG, PROPERTIES, OPEN_VIA_CLIENT, ONLYOFFICE_CONVERT,
|
||||
CONVERT_AND_EXPORT, CONVERT_TO_MARKDOWN, CONVERT_TO_DOCX, EXPORT_DOCX, CONVERT_TO_SDOC, EXPORT_SDOC
|
||||
} = TextTranslation;
|
||||
@ -613,9 +613,6 @@ export const Utils = {
|
||||
}
|
||||
|
||||
if (permission == 'rw') {
|
||||
if (enableFileTags) {
|
||||
list.push(TAGS);
|
||||
}
|
||||
if (isPro) {
|
||||
if (dirent.is_locked) {
|
||||
if (dirent.locked_by_me || dirent.lock_owner == 'OnlineOffice' || isRepoOwner || currentRepoInfo.is_admin) {
|
||||
|
Loading…
Reference in New Issue
Block a user