@@ -180,7 +180,7 @@ const Cell = React.memo(({
);
}
return columnFormatter;
- }, [isNameColumn, column, isCellSelected, cellValue, record, showRecordAsTree, nodeDepth, hasSubNodes, isFoldedNode, modifyRecord, toggleExpandNode]);
+ }, [isNameColumn, column, isCellSelected, cellValue, record, showRecordAsTree, nodeDepth, hasChildNodes, isFoldedNode, modifyRecord, toggleExpandNode]);
return (
@@ -213,7 +213,7 @@ Cell.propTypes = {
bgColor: PropTypes.string,
showRecordAsTree: PropTypes.bool,
nodeDepth: PropTypes.number,
- hasSubNodes: PropTypes.bool,
+ hasChildNodes: PropTypes.bool,
isFoldedNode: PropTypes.bool,
toggleExpandNode: PropTypes.func,
};
diff --git a/frontend/src/components/sf-table/table-main/records/record/index.js b/frontend/src/components/sf-table/table-main/records/record/index.js
index 795ce6e975..dc85fa0ff2 100644
--- a/frontend/src/components/sf-table/table-main/records/record/index.js
+++ b/frontend/src/components/sf-table/table-main/records/record/index.js
@@ -38,7 +38,7 @@ class Record extends React.Component {
nextProps.showRecordAsTree !== this.props.showRecordAsTree ||
nextProps.nodeKey !== this.props.nodeKey ||
nextProps.nodeDepth !== this.props.nodeDepth ||
- nextProps.hasSubNodes !== this.props.hasSubNodes ||
+ nextProps.hasChildNodes !== this.props.hasChildNodes ||
nextProps.treeNodeDisplayIndex !== this.props.treeNodeDisplayIndex ||
nextProps.isFoldedNode !== this.props.isFoldedNode
);
@@ -117,7 +117,7 @@ class Record extends React.Component {
bgColor={bgColor}
showRecordAsTree={this.props.showRecordAsTree}
nodeDepth={this.props.nodeDepth}
- hasSubNodes={this.props.hasSubNodes}
+ hasChildNodes={this.props.hasChildNodes}
isFoldedNode={this.props.isFoldedNode}
toggleExpandNode={this.props.toggleExpandNode}
/>
@@ -184,7 +184,7 @@ class Record extends React.Component {
bgColor={bgColor}
showRecordAsTree={this.props.showRecordAsTree}
nodeDepth={this.props.nodeDepth}
- hasSubNodes={this.props.hasSubNodes}
+ hasChildNodes={this.props.hasChildNodes}
isFoldedNode={this.props.isFoldedNode}
toggleExpandNode={this.props.toggleExpandNode}
/>
@@ -318,7 +318,7 @@ Record.propTypes = {
showRecordAsTree: PropTypes.bool,
nodeKey: PropTypes.string,
nodeDepth: PropTypes.number,
- hasSubNodes: PropTypes.bool,
+ hasChildNodes: PropTypes.bool,
treeNodeDisplayIndex: PropTypes.number,
isFoldedNode: PropTypes.bool,
toggleExpandNode: PropTypes.func,
diff --git a/frontend/src/components/sf-table/table-main/records/tree-body.js b/frontend/src/components/sf-table/table-main/records/tree-body.js
index 95d39a4dc2..1bc1927ba8 100644
--- a/frontend/src/components/sf-table/table-main/records/tree-body.js
+++ b/frontend/src/components/sf-table/table-main/records/tree-body.js
@@ -6,7 +6,7 @@ import InteractionMasks from '../../masks/interaction-masks';
import Record from './record';
import EventBus from '../../../common/event-bus';
import { EVENT_BUS_TYPE } from '../../constants/event-bus-type';
-import { checkIsTreeNodeShown, getTreeNodeId, getTreeNodeKey, getValidKeyTreeNodeFoldedMap } from '../../utils/tree';
+import { checkIsTreeNodeShown, checkTreeNodeHasChildNodes, getTreeNodeId, getTreeNodeKey, getValidKeyTreeNodeFoldedMap } from '../../utils/tree';
import { isShiftKeyDown } from '../../../../utils/keyboard-utils';
import { getColumnScrollPosition, getColVisibleStartIdx, getColVisibleEndIdx } from '../../utils/records-body-utils';
import { checkEditableViaClickCell, checkIsColumnSupportDirectEdit, getColumnByIndex, getColumnIndexByKey } from '../../utils/column';
@@ -535,7 +535,8 @@ class TreeBody extends Component {
const rowHeight = this.getRowHeight();
const cellMetaData = this.getCellMetaData();
let shownNodes = visibleNodes.map((node, index) => {
- const { _id: recordId, node_key, node_depth, has_sub_nodes, node_display_index } = node;
+ const { _id: recordId, node_key, node_depth, node_display_index } = node;
+ const hasChildNodes = checkTreeNodeHasChildNodes(node);
const record = this.props.recordGetterById(recordId);
const isSelected = TreeMetrics.checkIsTreeNodeSelected(node_key, treeMetrics);
const recordIndex = startRenderIndex + index;
@@ -568,7 +569,7 @@ class TreeBody extends Component {
searchResult={this.props.searchResult}
nodeKey={node_key}
nodeDepth={node_depth}
- hasSubNodes={has_sub_nodes}
+ hasChildNodes={hasChildNodes}
isFoldedNode={isFoldedNode}
checkCanModifyRecord={this.props.checkCanModifyRecord}
checkCellValueChanged={this.props.checkCellValueChanged}
diff --git a/frontend/src/components/sf-table/utils/cell-comparer.js b/frontend/src/components/sf-table/utils/cell-comparer.js
index 64dcaa37a6..5c9d08b123 100644
--- a/frontend/src/components/sf-table/utils/cell-comparer.js
+++ b/frontend/src/components/sf-table/utils/cell-comparer.js
@@ -22,7 +22,7 @@ export const checkCellValueChanged = (oldVal, newVal) => {
export const cellCompare = (props, nextProps) => {
const {
record: oldRecord, column, isCellSelected, isLastCell, highlightClassName, height, bgColor,
- showRecordAsTree, nodeDepth, hasSubNodes, isFoldedNode,
+ showRecordAsTree, nodeDepth, hasChildNodes, isFoldedNode,
} = props;
const {
record: newRecord, highlightClassName: newHighlightClassName, height: newHeight, column: newColumn, bgColor: newBgColor,
@@ -50,7 +50,7 @@ export const cellCompare = (props, nextProps) => {
bgColor !== newBgColor ||
showRecordAsTree !== nextProps.showRecordAsTree ||
nodeDepth !== nextProps.nodeDepth ||
- hasSubNodes !== nextProps.hasSubNodes ||
+ hasChildNodes !== nextProps.hasChildNodes ||
isFoldedNode !== nextProps.isFoldedNode ||
props.groupRecordIndex !== nextProps.groupRecordIndex ||
props.recordIndex !== nextProps.recordIndex
diff --git a/frontend/src/components/sf-table/utils/tree.js b/frontend/src/components/sf-table/utils/tree.js
index 3b3ff4f802..0cdc1c8b14 100644
--- a/frontend/src/components/sf-table/utils/tree.js
+++ b/frontend/src/components/sf-table/utils/tree.js
@@ -1,11 +1,15 @@
import { TREE_NODE_KEY } from '../constants/tree';
-export const createTreeNode = (nodeId, nodeKey, depth, hasSubNodes) => {
+export const generateNodeKey = (parentKey, currentNodeId) => {
+ return `${parentKey ? parentKey + '_' : ''}${currentNodeId}`;
+};
+
+export const createTreeNode = (nodeId, nodeKey, depth, hasChildNodes) => {
return {
[TREE_NODE_KEY.ID]: nodeId,
[TREE_NODE_KEY.KEY]: nodeKey,
[TREE_NODE_KEY.DEPTH]: depth,
- [TREE_NODE_KEY.HAS_SUB_NODES]: hasSubNodes,
+ [TREE_NODE_KEY.HAS_CHILD_NODES]: hasChildNodes,
};
};
@@ -74,3 +78,56 @@ export const getTreeNodeId = (node) => {
export const getTreeNodeKey = (node) => {
return node ? node[TREE_NODE_KEY.KEY] : '';
};
+
+export const getTreeNodeDepth = (node) => {
+ return node ? node[TREE_NODE_KEY.DEPTH] : 0;
+};
+
+export const checkTreeNodeHasChildNodes = (node) => {
+ return node ? node[TREE_NODE_KEY.HAS_CHILD_NODES] : false;
+};
+
+export const resetTreeHasChildNodesStatus = (tree) => {
+ if (!Array.isArray(tree) || tree.length === 0) {
+ return;
+ }
+ tree.forEach((node, index) => {
+ const nextNode = tree[index + 1];
+ const nextNodeKey = getTreeNodeKey(nextNode);
+ const currentNodeKey = getTreeNodeKey(node);
+ if (nextNode && checkTreeNodeHasChildNodes(node) && !nextNodeKey.includes(currentNodeKey)) {
+ tree[index][TREE_NODE_KEY.HAS_CHILD_NODES] = false;
+ }
+ });
+};
+
+export const addTreeChildNode = (newChildNode, parentNode, tree) => {
+ if (!parentNode || !Array.isArray(tree) || tree.length === 0) {
+ return;
+ }
+ const parentNodeKey = getTreeNodeKey(parentNode);
+ const parentNodeDepth = getTreeNodeDepth(parentNode);
+ const parentNodeIndex = tree.findIndex((node) => getTreeNodeKey(node) === parentNodeKey);
+ if (parentNodeIndex < 0) {
+ return;
+ }
+
+ if (!checkTreeNodeHasChildNodes(parentNode)) {
+ tree[parentNodeIndex] = { ...parentNode, [TREE_NODE_KEY.HAS_CHILD_NODES]: true };
+ }
+
+ const childNodeDepth = parentNodeDepth + 1;
+ let lastChildNodeIndex = parentNodeIndex;
+ for (let i = parentNodeIndex + 1, len = tree.length; i < len; i++) {
+ const currentNode = tree[i];
+ if (!getTreeNodeKey(currentNode).includes(parentNodeKey)) {
+ break;
+ }
+
+ // insert new child tag behind the last child tag
+ if (getTreeNodeDepth(currentNode) === childNodeDepth) {
+ lastChildNodeIndex = i;
+ }
+ }
+ tree.splice(lastChildNodeIndex + 1, 0, newChildNode);
+};
diff --git a/frontend/src/tag/components/popover/set-linked-tags-popover/add-linked-tags.js b/frontend/src/tag/components/popover/set-linked-tags-popover/add-linked-tags.js
deleted file mode 100644
index 1e5ceb2045..0000000000
--- a/frontend/src/tag/components/popover/set-linked-tags-popover/add-linked-tags.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import PropTypes from 'prop-types';
-import { SearchInput } from '@seafile/sf-metadata-ui-component';
-import EmptyTip from '../../../../components/empty-tip';
-import Tags from './tags';
-import { KeyCodes } from '../../../../constants';
-import { gettext } from '../../../../utils/constants';
-import { getTagsByNameOrColor } from '../../../utils';
-
-const getSelectableTags = (allTags, idTagSelectedMap) => {
- if (!idTagSelectedMap) {
- return allTags;
- }
- return allTags.filter((tag) => !idTagSelectedMap[tag._id]);
-};
-
-const initIdTagSelectedMap = (linkedTags) => {
- let idTagSelectedMap = {};
- linkedTags.forEach((linkedTag) => {
- idTagSelectedMap[linkedTag._id] = true;
- });
- return idTagSelectedMap;
-};
-
-const AddLinkedTags = ({ allTags, linkedTags, switchToLinkedTagsPage, addLinkedTag, deleteLinedTag }) => {
- const initialIdTagSelectedMap = initIdTagSelectedMap(linkedTags);
- const [idTagSelectedMap, setIdSelectedMap] = useState(initialIdTagSelectedMap);
- const [selectableTags, setSelectableTags] = useState([]);
- const [searchValue, setSearchValue] = useState('');
-
- const initialSelectableTagsRef = useRef(getSelectableTags(allTags, initialIdTagSelectedMap));
-
- const selectTag = useCallback((tag) => {
- let updatedIdTagSelectedMap = { ...idTagSelectedMap };
- if (updatedIdTagSelectedMap[tag._id]) {
- delete updatedIdTagSelectedMap[tag._id];
- setIdSelectedMap(updatedIdTagSelectedMap);
- deleteLinedTag(tag._id);
- } else {
- updatedIdTagSelectedMap[tag._id] = true;
- setIdSelectedMap(updatedIdTagSelectedMap);
- addLinkedTag(tag);
- }
-
- }, [idTagSelectedMap, addLinkedTag, deleteLinedTag]);
-
- const onKeyDown = useCallback((event) => {
- if (
- event.keyCode === KeyCodes.ChineseInputMethod ||
- event.keyCode === KeyCodes.Enter ||
- event.keyCode === KeyCodes.LeftArrow ||
- event.keyCode === KeyCodes.RightArrow
- ) {
- event.stopPropagation();
- }
- }, []);
-
- const onChangeSearch = useCallback((newSearchValue) => {
- if (searchValue === newSearchValue) return;
- setSearchValue(newSearchValue);
- }, [searchValue]);
-
- useEffect(() => {
- let searchedTags = [];
- if (!searchValue) {
- searchedTags = [...initialSelectableTagsRef.current];
- } else {
- searchedTags = getTagsByNameOrColor(initialSelectableTagsRef.current, searchValue);
- }
- setSelectableTags(searchedTags);
- }, [searchValue, allTags]);
-
- return (
-
-
-
-
- {gettext('Link existing tags')}
-
-
-
-
-
-
- {selectableTags.length === 0 && (
-
- )}
- {selectableTags.length > 0 && (
-
-
-
- )}
-
-
- );
-};
-
-AddLinkedTags.propTypes = {
- isParentTags: PropTypes.bool,
- allTags: PropTypes.array,
- linkedTags: PropTypes.array,
- switchToLinkedTagsPage: PropTypes.func,
- addLinkedTag: PropTypes.func,
- deleteLinedTag: PropTypes.func,
-};
-
-export default AddLinkedTags;
diff --git a/frontend/src/tag/components/popover/set-linked-tags-popover/index.css b/frontend/src/tag/components/popover/set-linked-tags-popover/index.css
deleted file mode 100644
index 80198fec0c..0000000000
--- a/frontend/src/tag/components/popover/set-linked-tags-popover/index.css
+++ /dev/null
@@ -1,134 +0,0 @@
-.sf-metadata-set-linked-tags-popover .popover {
- max-width: unset;
-}
-
-.sf-metadata-set-linked-tags-popover-selected,
-.sf-metadata-set-linked-tags-popover-selector {
- display: flex;
- flex-direction: column;
- height: 100%;
-}
-
-.sf-metadata-set-linked-tags-popover-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- height: 46px;
- border-bottom: 1px solid #e9ecef;
-}
-
-.sf-metadata-set-linked-tags-popover-title {
- display: inline-flex;
- align-items: center;
- height: 40px;
- line-height: 40px;
- padding: 0 20px;
- font-size: 16px;
- font-weight: 500;
-}
-
-.sf-metadata-set-linked-tags-popover-selector .sf-metadata-set-linked-tags-popover-title {
- margin-left: -3px;
-}
-
-.sf-metadata-set-linked-tags-popover-body {
- flex: 1;
- height: calc(100% - 46px);
-}
-
-.sf-metadata-set-linked-tags-popover .empty-tip {
- margin-top: 0;
-}
-
-.sf-metadata-set-linked-tags-popover-selectable-tags-wrapper {
- height: calc(100% - 48px);
-}
-
-.sf-metadata-editing-tags-list {
- height: 100%;
- padding: 10px 20px;
- overflow-y: scroll;
-}
-
-.sf-metadata-editing-tags-list.selectable:hover {
- cursor: pointer;
-}
-
-.sf-metadata-editing-tag:hover {
- background: #f5f5f5;
-}
-
-.sf-metadata-editing-tag-container {
- display: flex;
- align-items: center;
- height: 30px;
- width: 100%;
- border-radius: 2px;
- font-size: 13px;
- color: #212529;
-}
-
-.sf-metadata-set-linked-tags-popover-search-container {
- height: 48px;
- padding: 10px 20px 0;
-}
-
-.sf-metadata-editing-tag-color-name {
- display: flex;
- align-items: center;
- flex: 1;
-}
-
-.sf-metadata-editing-tag-color-name .sf-metadata-tag-color {
- height: 14px;
- width: 14px;
- border-radius: 50%;
- flex-shrink: 0;
-}
-
-.sf-metadata-editing-tag-color-name .sf-metadata-tag-name {
- flex: 1;
- margin-left: 4px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.sf-metadata-editing-tag-operation {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 24px;
- height: 24px;
- cursor: pointer;
-}
-
-.sf-metadata-editing-tag-delete-icon {
- font-size: 14px;
-}
-
-.sf-metadata-editing-tag-delete:hover .sf-metadata-editing-tag-delete-icon {
- fill: #555;
-}
-
-.sf-metadata-set-linked-tags-popover-header-operation {
- display: inline-flex;
- width: 24px;
- height: 24px;
- margin-right: 3px;
- align-items: center;
- justify-content: center;
-}
-
-.sf-metadata-set-linked-tags-popover-header-operation:hover {
- border-radius: 3px;
- background-color: #efefef;
- cursor: pointer;
-}
-
-.sf-metadata-set-linked-tags-popover-back-icon {
- display: inline-block;
- transform: rotate(180deg);
- font-size: 14px;
- color: #666;
-}
diff --git a/frontend/src/tag/components/popover/set-linked-tags-popover/index.js b/frontend/src/tag/components/popover/set-linked-tags-popover/index.js
deleted file mode 100644
index f2f41a1c91..0000000000
--- a/frontend/src/tag/components/popover/set-linked-tags-popover/index.js
+++ /dev/null
@@ -1,135 +0,0 @@
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import PropTypes from 'prop-types';
-import { UncontrolledPopover } from 'reactstrap';
-import isHotkey from 'is-hotkey';
-import LinkedTags from './linked-tags';
-import AddLinkedTags from './add-linked-tags';
-import { getRowsByIds } from '../../../../metadata/utils/table';
-import { useTags } from '../../../hooks';
-import { getEventClassName } from '../../../../metadata/utils/common';
-
-import './index.css';
-
-const POPOVER_WIDTH = 560;
-const POPOVER_WINDOW_SAFE_SPACE = 30;
-const POPOVER_MAX_HEIGHT = 520;
-const POPOVER_MIN_HEIGHT = 300;
-
-const KEY_MODE_TYPE = {
- LINKED_TAGS: 'linked_tags',
- ADD_LINKED_TAGS: 'add_linked_tags',
-};
-
-const SetLinkedTagsPopover = ({ isParentTags, target, placement, tagLinks, allTags, hidePopover, addTagLinks, deleteTagLinks }) => {
- const { tagsData } = useTags();
- const linkedRowsIds = Array.isArray(tagLinks) ? tagLinks.map((link) => link.row_id) : [];
- const initialLinkedTags = getRowsByIds(tagsData, linkedRowsIds);
- const [mode, setMode] = useState(KEY_MODE_TYPE.LINKED_TAGS);
- const [linkedTags, setLinkedTags] = useState(initialLinkedTags);
-
- const popoverRef = useRef(null);
-
- const getPopoverInnerStyle = () => {
- let style = { width: POPOVER_WIDTH };
- const windowHeight = window.innerHeight - POPOVER_WINDOW_SAFE_SPACE;
- let maxHeight = POPOVER_MAX_HEIGHT;
- if (windowHeight < maxHeight) {
- maxHeight = windowHeight;
- }
- if (maxHeight < POPOVER_MIN_HEIGHT) {
- maxHeight = POPOVER_MIN_HEIGHT;
- }
- style.height = maxHeight;
- return style;
- };
-
- const onHidePopover = useCallback((event) => {
- if (popoverRef.current && !getEventClassName(event).includes('popover') && !popoverRef.current.contains(event.target)) {
- hidePopover(event);
- event.preventDefault();
- event.stopPropagation();
- return false;
- }
- }, [hidePopover]);
-
- const onHotKey = useCallback((event) => {
- if (isHotkey('esc', event)) {
- event.preventDefault();
- hidePopover();
- }
- }, [hidePopover]);
-
- useEffect(() => {
- document.addEventListener('click', onHidePopover, true);
- document.addEventListener('keydown', onHotKey);
- return () => {
- document.removeEventListener('click', onHidePopover, true);
- document.removeEventListener('keydown', onHotKey);
- };
- }, [onHidePopover, onHotKey]);
-
- const deleteLinedTag = useCallback((tagId) => {
- let updatedLinkedTags = [...linkedTags];
- const deleteIndex = updatedLinkedTags.findIndex((tag) => tag._id === tagId);
- if (deleteIndex < 0) return;
- updatedLinkedTags.splice(deleteIndex, 1);
- setLinkedTags(updatedLinkedTags);
- deleteTagLinks(tagId);
- }, [linkedTags, deleteTagLinks]);
-
- const addLinkedTag = useCallback((tag) => {
- let updatedLinkedTags = [...linkedTags];
- updatedLinkedTags.push(tag);
- setLinkedTags(updatedLinkedTags);
- addTagLinks(tag);
- }, [linkedTags, addTagLinks]);
-
- return (
-
-
- {mode === KEY_MODE_TYPE.LINKED_TAGS ?
-
setMode(KEY_MODE_TYPE.ADD_LINKED_TAGS)}
- deleteLinedTag={deleteLinedTag}
- /> :
- setMode(KEY_MODE_TYPE.LINKED_TAGS)}
- addLinkedTag={addLinkedTag}
- deleteLinedTag={deleteLinedTag}
- />
- }
-
-
- );
-};
-
-SetLinkedTagsPopover.propTypes = {
- isParentTags: PropTypes.bool,
- placement: PropTypes.string,
- target: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
- tagLinks: PropTypes.array,
- allTags: PropTypes.array,
- hidePopover: PropTypes.func,
- addTagLinks: PropTypes.func,
- deleteTagLinks: PropTypes.func,
-};
-
-SetLinkedTagsPopover.defaultProps = {
- placement: 'bottom-end',
-};
-
-export default SetLinkedTagsPopover;
diff --git a/frontend/src/tag/components/popover/set-linked-tags-popover/linked-tags.js b/frontend/src/tag/components/popover/set-linked-tags-popover/linked-tags.js
deleted file mode 100644
index e1aedcf889..0000000000
--- a/frontend/src/tag/components/popover/set-linked-tags-popover/linked-tags.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Button } from 'reactstrap';
-import EmptyTip from '../../../../components/empty-tip';
-import Tags from './tags';
-import { gettext } from '../../../../utils/constants';
-
-const LinkedTags = ({ isParentTags, linkedTags, switchToAddTagsPage, deleteLinedTag }) => {
-
- return (
-
-
-
- {isParentTags ? gettext('Parent tags') : gettext('Sub tags')}
-
-
-
-
- {linkedTags.length === 0 ?
-
- :
-
- }
-
-
- );
-};
-
-LinkedTags.propTypes = {
- isParentTags: PropTypes.bool,
- linkedTags: PropTypes.array,
- switchToAddTagsPage: PropTypes.func,
- deleteTag: PropTypes.func,
-};
-
-export default LinkedTags;
diff --git a/frontend/src/tag/components/popover/set-linked-tags-popover/tags.js b/frontend/src/tag/components/popover/set-linked-tags-popover/tags.js
deleted file mode 100644
index a3314406c3..0000000000
--- a/frontend/src/tag/components/popover/set-linked-tags-popover/tags.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import React, { useCallback } from 'react';
-import PropTypes from 'prop-types';
-import classnames from 'classnames';
-import { Icon } from '@seafile/sf-metadata-ui-component';
-import { getTagColor, getTagId, getTagName } from '../../../utils';
-import { debounce } from '../../../../metadata/utils/common';
-
-const Tags = ({ tags, deletable, selectable, idTagSelectedMap, selectTag, deleteTag }) => {
-
- const clickTag = debounce((tag) => {
- if (!selectable) return;
- selectTag && selectTag(tag);
- }, 200);
-
- const remove = useCallback((tagId) => {
- if (!deletable) return;
- deleteTag && deleteTag(tagId);
- }, [deletable, deleteTag]);
-
- return (
-
- {tags.map((tag) => {
- const tagId = getTagId(tag);
- const tagName = getTagName(tag);
- const tagColor = getTagColor(tag);
- return (
-
clickTag(tag)}>
-
-
-
- {deletable &&
remove(tagId)}>
}
- {(selectable && idTagSelectedMap && idTagSelectedMap[tagId]) &&
}
-
-
-
- );
- })}
-
- );
-};
-
-Tags.propTypes = {
- tags: PropTypes.array,
- deletable: PropTypes.bool,
- selectable: PropTypes.bool,
- selectTag: PropTypes.func,
- deleteTag: PropTypes.func,
-};
-
-export default Tags;
diff --git a/frontend/src/tag/hooks/tags.js b/frontend/src/tag/hooks/tags.js
index f7c9bc0224..5509287585 100644
--- a/frontend/src/tag/hooks/tags.js
+++ b/frontend/src/tag/hooks/tags.js
@@ -124,6 +124,10 @@ export const TagsProvider = ({ repoID, currentPath, selectTagsView, children, ..
return storeRef.current.addTags(rows, callback);
}, []);
+ const addChildTag = useCallback((tagData, parentTagId, callback = {}) => {
+ return storeRef.current.addChildTag(tagData, parentTagId, callback);
+ }, []);
+
const modifyTags = useCallback((tagIds, idTagUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, { success_callback, fail_callback }) => {
storeRef.current.modifyTags(tagIds, idTagUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, { success_callback, fail_callback });
}, [storeRef]);
@@ -262,6 +266,7 @@ export const TagsProvider = ({ repoID, currentPath, selectTagsView, children, ..
updateCurrentDirent: params.updateCurrentDirent,
addTag,
addTags,
+ addChildTag,
modifyTags,
deleteTags,
duplicateTag,
diff --git a/frontend/src/tag/store/data-processor.js b/frontend/src/tag/store/data-processor.js
index 9cdfbfbe50..e8d187fd07 100644
--- a/frontend/src/tag/store/data-processor.js
+++ b/frontend/src/tag/store/data-processor.js
@@ -6,7 +6,10 @@ import { OPERATION_TYPE } from './operations';
import { buildTagsTree } from '../utils/tree';
import { getRecordIdFromRecord } from '../../metadata/utils/cell';
import { TREE_NODE_KEY } from '../../components/sf-table/constants/tree';
-import { createTreeNode } from '../../components/sf-table/utils/tree';
+import {
+ addTreeChildNode, createTreeNode, generateNodeKey, getTreeNodeDepth, getTreeNodeId, getTreeNodeKey,
+ resetTreeHasChildNodesStatus,
+} from '../../components/sf-table/utils/tree';
// const DEFAULT_COMPUTER_PROPERTIES_CONTROLLER = {
// isUpdateSummaries: true,
@@ -27,7 +30,7 @@ class DataProcessor {
let updated_rows_tree = [...rows_tree];
tags.forEach((tag) => {
const tagId = getRecordIdFromRecord(tag);
- const nodeKey = tagId;
+ const nodeKey = generateNodeKey('', tagId);
const node = createTreeNode(tagId, nodeKey, 0, false);
updated_rows_tree.push(node);
});
@@ -38,20 +41,23 @@ class DataProcessor {
if (!Array.isArray(deletedTagsIds) || deletedTagsIds.length === 0) return;
const { rows_tree } = table;
const idTagDeletedMap = deletedTagsIds.reduce((currIdTagDeletedMap, tagId) => ({ ...currIdTagDeletedMap, [tagId]: true }), {});
- const hasDeletedParentNode = rows_tree.some((node) => idTagDeletedMap[node[TREE_NODE_KEY.ID]] && node[TREE_NODE_KEY.HAS_SUB_NODES]);
+ const hasDeletedParentNode = rows_tree.some((node) => idTagDeletedMap[node[TREE_NODE_KEY.ID]] && node[TREE_NODE_KEY.HAS_CHILD_NODES]);
if (hasDeletedParentNode) {
// need re-build tree if some parent nodes deleted
this.buildTagsTree(table.rows, table);
return;
}
- // remove the nodes which has no sub nodes directly
+ // remove the nodes which has no child nodes directly
let updated_rows_tree = [];
rows_tree.forEach((node) => {
if (!idTagDeletedMap[node[TREE_NODE_KEY.ID]]) {
updated_rows_tree.push(node);
}
});
+
+ // update has_child_nodes status(all child nodes may be deleted)
+ resetTreeHasChildNodesStatus(updated_rows_tree);
table.rows_tree = updated_rows_tree;
}
@@ -198,6 +204,23 @@ class DataProcessor {
this.updateTagsTreeWithNewTags(tags, table);
break;
}
+ case OPERATION_TYPE.ADD_CHILD_TAG: {
+ const { tag, parent_tag_id } = operation;
+ const tagId = getRecordIdFromRecord(tag);
+ if (!tagId || !parent_tag_id) return;
+ const { rows_tree } = table;
+ rows_tree.forEach((node) => {
+ const nodeId = getTreeNodeId(node);
+ if (nodeId === parent_tag_id) {
+ const parentNodeKey = getTreeNodeKey(node);
+ const parentNodeDepth = getTreeNodeDepth(node);
+ const subNodeKey = generateNodeKey(parentNodeKey, tagId);
+ const childNode = createTreeNode(tagId, subNodeKey, parentNodeDepth + 1, false);
+ addTreeChildNode(childNode, node, rows_tree);
+ }
+ });
+ break;
+ }
case OPERATION_TYPE.ADD_TAG_LINKS:
case OPERATION_TYPE.DELETE_TAG_LINKS: {
this.buildTagsTree(table.rows, table);
diff --git a/frontend/src/tag/store/index.js b/frontend/src/tag/store/index.js
index 2bb00c29c3..0cf703fcbe 100644
--- a/frontend/src/tag/store/index.js
+++ b/frontend/src/tag/store/index.js
@@ -250,6 +250,19 @@ class Store {
this.applyOperation(operation);
}
+ addChildTag(tagData, parentTagId, { fail_callback, success_callback } = {}) {
+ const type = OPERATION_TYPE.ADD_CHILD_TAG;
+ const operation = this.createOperation({
+ type,
+ repo_id: this.repoId,
+ tag_data: tagData,
+ parent_tag_id: parentTagId,
+ fail_callback,
+ success_callback,
+ });
+ this.applyOperation(operation);
+ }
+
modifyTags(row_ids, id_row_updates, id_original_row_updates, id_old_row_data, id_original_old_row_data, { fail_callback, success_callback }) {
const originalRows = getRowsByIds(this.data, row_ids);
let valid_row_ids = [];
diff --git a/frontend/src/tag/store/operations/apply.js b/frontend/src/tag/store/operations/apply.js
index 0e58b443b2..4da72ce5fd 100644
--- a/frontend/src/tag/store/operations/apply.js
+++ b/frontend/src/tag/store/operations/apply.js
@@ -5,6 +5,7 @@ import { OPERATION_TYPE } from './constants';
import { PRIVATE_COLUMN_KEY } from '../../constants';
import { username } from '../../../utils/constants';
import { addRowLinks, removeRowLinks } from '../../utils/link';
+import { getRecordIdFromRecord } from '../../../metadata/utils/cell';
dayjs.extend(utc);
@@ -30,6 +31,34 @@ export default function apply(data, operation) {
data.rows = updatedRows;
return data;
}
+ case OPERATION_TYPE.ADD_CHILD_TAG: {
+ const { tag, parent_tag_id } = operation;
+ const tagId = getRecordIdFromRecord(tag);
+ if (!tagId || !parent_tag_id) {
+ return data;
+ }
+
+ const { rows } = data;
+ const updatedRows = [...rows];
+
+ // add parent link
+ const updatedTag = addRowLinks(tag, PRIVATE_COLUMN_KEY.PARENT_LINKS, [parent_tag_id]);
+
+ // add child link
+ const parentTagIndex = rows.findIndex((tag) => getRecordIdFromRecord(tag) === parent_tag_id);
+ if (parentTagIndex > -1) {
+ const parentTag = updatedRows[parentTagIndex];
+ const updatedParentTag = addRowLinks(parentTag, PRIVATE_COLUMN_KEY.SUB_LINKS, [tagId]);
+ updatedRows[parentTagIndex] = updatedParentTag;
+ data.id_row_map[parent_tag_id] = updatedParentTag;
+ }
+
+ updatedRows.push(updatedTag);
+ data.row_ids.push(tagId);
+ data.id_row_map[tagId] = updatedTag;
+ data.rows = updatedRows;
+ return data;
+ }
case OPERATION_TYPE.MODIFY_RECORDS:
case OPERATION_TYPE.MODIFY_LOCAL_RECORDS: {
const { id_original_row_updates, id_row_updates } = operation;
@@ -129,7 +158,7 @@ export default function apply(data, operation) {
data.id_row_map[currentRowId] = updatedRow;
}
if (other_rows_ids.includes(currentRowId)) {
- // add current tag as sub tag to related tags
+ // add current tag as child tag to related tags
updatedRow = addRowLinks(updatedRow, PRIVATE_COLUMN_KEY.SUB_LINKS, [row_id]);
data.rows[index] = updatedRow;
data.id_row_map[currentRowId] = updatedRow;
@@ -140,7 +169,7 @@ export default function apply(data, operation) {
const currentRowId = row._id;
let updatedRow = { ...row };
if (currentRowId === row_id) {
- // add sub tags to current tag
+ // add child tags to current tag
updatedRow = addRowLinks(updatedRow, PRIVATE_COLUMN_KEY.SUB_LINKS, other_rows_ids);
data.rows[index] = updatedRow;
data.id_row_map[currentRowId] = updatedRow;
@@ -169,7 +198,7 @@ export default function apply(data, operation) {
data.id_row_map[currentRowId] = updatedRow;
}
if (other_rows_ids.includes(currentRowId)) {
- // remove current tag as sub tag from related tags
+ // remove current tag as child tag from related tags
updatedRow = removeRowLinks(updatedRow, PRIVATE_COLUMN_KEY.SUB_LINKS, [row_id]);
data.rows[index] = updatedRow;
data.id_row_map[currentRowId] = updatedRow;
@@ -180,7 +209,7 @@ export default function apply(data, operation) {
const currentRowId = row._id;
let updatedRow = { ...row };
if (currentRowId === row_id) {
- // remove sub tags from current tag
+ // remove child tags from current tag
updatedRow = removeRowLinks(updatedRow, PRIVATE_COLUMN_KEY.SUB_LINKS, other_rows_ids);
data.rows[index] = updatedRow;
data.id_row_map[currentRowId] = updatedRow;
diff --git a/frontend/src/tag/store/operations/constants.js b/frontend/src/tag/store/operations/constants.js
index 26ef311a96..672afd9e78 100644
--- a/frontend/src/tag/store/operations/constants.js
+++ b/frontend/src/tag/store/operations/constants.js
@@ -1,5 +1,6 @@
export const OPERATION_TYPE = {
ADD_RECORDS: 'add_records',
+ ADD_CHILD_TAG: 'add_child_tag',
MODIFY_RECORDS: 'modify_records',
DELETE_RECORDS: 'delete_records',
RESTORE_RECORDS: 'restore_records',
@@ -14,6 +15,7 @@ export const OPERATION_TYPE = {
export const OPERATION_ATTRIBUTES = {
[OPERATION_TYPE.ADD_RECORDS]: ['repo_id', 'rows', 'tags'],
+ [OPERATION_TYPE.ADD_CHILD_TAG]: ['repo_id', 'tag_data', 'parent_tag_id'],
[OPERATION_TYPE.MODIFY_RECORDS]: ['repo_id', 'row_ids', 'id_row_updates', 'id_original_row_updates', 'id_old_row_data', 'id_original_old_row_data', 'is_copy_paste', 'is_rename', 'id_obj_id'],
[OPERATION_TYPE.DELETE_RECORDS]: ['repo_id', 'tag_ids', 'deleted_tags'],
[OPERATION_TYPE.RESTORE_RECORDS]: ['repo_id', 'rows_data', 'original_rows', 'link_infos', 'upper_row_ids'],
@@ -38,6 +40,7 @@ export const LOCAL_APPLY_OPERATION_TYPE = [
// apply operation after exec operation on the server
export const NEED_APPLY_AFTER_SERVER_OPERATION = [
OPERATION_TYPE.ADD_RECORDS,
+ OPERATION_TYPE.ADD_CHILD_TAG,
];
export const VIEW_OPERATION = [
diff --git a/frontend/src/tag/store/server-operator.js b/frontend/src/tag/store/server-operator.js
index 9559cee74a..879a013eb0 100644
--- a/frontend/src/tag/store/server-operator.js
+++ b/frontend/src/tag/store/server-operator.js
@@ -2,6 +2,7 @@ import { gettext } from '../../utils/constants';
import { OPERATION_TYPE } from './operations';
import { getColumnByKey } from '../../metadata/utils/column';
import ObjectUtils from '../../metadata/utils/object-utils';
+import { PRIVATE_COLUMN_KEY } from '../constants';
const MAX_LOAD_RECORDS = 100;
@@ -26,6 +27,27 @@ class ServerOperator {
});
break;
}
+ case OPERATION_TYPE.ADD_CHILD_TAG: {
+ const { tag_data, parent_tag_id } = operation;
+ this.context.addTags([tag_data]).then(res => {
+ const tags = res?.data?.tags || [];
+ const childTag = tags[0];
+ if (!childTag) {
+ callback({ error: gettext('Failed to add tags') });
+ return;
+ }
+
+ operation.tag = childTag;
+
+ // set parent tag for new child tag
+ const id_linked_rows_ids_map = { [childTag._id]: [parent_tag_id] };
+ this.context.addTagLinks(PRIVATE_COLUMN_KEY.PARENT_LINKS, id_linked_rows_ids_map);
+ callback({ operation });
+ }).catch(error => {
+ callback({ error: gettext('Failed to add tags') });
+ });
+ break;
+ }
case OPERATION_TYPE.MODIFY_RECORDS: {
const { row_ids, id_row_updates } = operation;
const recordsData = row_ids.map(rowId => {
diff --git a/frontend/src/tag/utils/cell.js b/frontend/src/tag/utils/cell.js
index de1d604eb4..a6232767ad 100644
--- a/frontend/src/tag/utils/cell.js
+++ b/frontend/src/tag/utils/cell.js
@@ -28,15 +28,10 @@ export const getParentLinks = (tag) => {
return (tag && tag[PRIVATE_COLUMN_KEY.PARENT_LINKS]) || [];
};
-export const getSubLinks = (tag) => {
+export const getChildLinks = (tag) => {
return (tag && tag[PRIVATE_COLUMN_KEY.SUB_LINKS]) || [];
};
-export const getSubTagsCount = (tag) => {
- const subLinks = getSubLinks(tag);
- return subLinks.length;
-};
-
export const getTagFilesCount = (tag) => {
const links = tag ? tag[PRIVATE_COLUMN_KEY.TAG_FILE_LINKS] : [];
if (Array.isArray(links)) return links.length;
diff --git a/frontend/src/tag/utils/tree.js b/frontend/src/tag/utils/tree.js
index db8ff042f5..07406289ac 100644
--- a/frontend/src/tag/utils/tree.js
+++ b/frontend/src/tag/utils/tree.js
@@ -1,16 +1,16 @@
-import { createTreeNode } from '../../components/sf-table/utils/tree';
+import { createTreeNode, generateNodeKey } from '../../components/sf-table/utils/tree';
import { getRecordIdFromRecord } from '../../metadata/utils/cell';
import { getRowsByIds } from '../../metadata/utils/table';
-import { getParentLinks, getSubLinks } from './cell';
+import { getParentLinks, getChildLinks } from './cell';
-const setSubNodes = (row, parentDepth, parentKey, idNodeInCurrentTreeMap, idNodeCreatedMap, tree, table) => {
+const setChildNodes = (row, parentDepth, parentKey, idNodeInCurrentTreeMap, idNodeCreatedMap, tree, table) => {
const nodeId = getRecordIdFromRecord(row);
idNodeCreatedMap[nodeId] = true;
idNodeInCurrentTreeMap[nodeId] = true; // for preventing circular dependencies
- const nodeKey = `${parentKey ? parentKey + '_' : ''}${nodeId}`; // the unique ID of each node
- const subLinks = getSubLinks(row);
+ const nodeKey = generateNodeKey(parentKey, nodeId); // the unique ID of each node
+ const subLinks = getChildLinks(row);
const subRowsIds = subLinks.map((link) => link.row_id);
const subRows = getRowsByIds(table, subRowsIds);
const validSubRows = subRows.filter((row) => row && !idNodeInCurrentTreeMap[row._id]);
@@ -21,7 +21,7 @@ const setSubNodes = (row, parentDepth, parentKey, idNodeInCurrentTreeMap, idNode
if (validSubRows) {
const nextNodeDepth = parentDepth + 1;
validSubRows.forEach((subRow) => {
- setSubNodes(subRow, nextNodeDepth, nodeKey, { ...idNodeInCurrentTreeMap }, idNodeCreatedMap, tree, table);
+ setChildNodes(subRow, nextNodeDepth, nodeKey, { ...idNodeInCurrentTreeMap }, idNodeCreatedMap, tree, table);
});
}
@@ -33,7 +33,7 @@ const setSubNodes = (row, parentDepth, parentKey, idNodeInCurrentTreeMap, idNode
* @param {array} rows tags
* @returns {array} tree
* tree: [
- * { _id, node_depth, node_key, has_sub_nodes, ... }
+ * { _id, node_depth, node_key, has_child_nodes, ... }
* ...
* ]
*/
@@ -44,7 +44,7 @@ export const buildTagsTree = (rows, table) => {
const nodeId = getRecordIdFromRecord(row);
const parentLinks = getParentLinks(row);
if (parentLinks.length === 0 && !idNodeCreatedMap[nodeId]) {
- setSubNodes(row, 0, '', {}, idNodeCreatedMap, tree, table);
+ setChildNodes(row, 0, '', {}, idNodeCreatedMap, tree, table);
}
});
@@ -53,7 +53,7 @@ export const buildTagsTree = (rows, table) => {
noneCreatedRows.forEach((row) => {
const nodeId = getRecordIdFromRecord(row);
if (!idNodeCreatedMap[nodeId]) {
- setSubNodes(row, 0, '', {}, idNodeCreatedMap, tree, table);
+ setChildNodes(row, 0, '', {}, idNodeCreatedMap, tree, table);
}
});
diff --git a/frontend/src/tag/views/all-tags/tags-table/columns-factory.js b/frontend/src/tag/views/all-tags/tags-table/columns-factory.js
index f7727a6d63..cf77cd6d7a 100644
--- a/frontend/src/tag/views/all-tags/tags-table/columns-factory.js
+++ b/frontend/src/tag/views/all-tags/tags-table/columns-factory.js
@@ -13,7 +13,7 @@ const KEY_COLUMN_ICON_NAME = {
const KEY_COLUMN_DISPLAY_NAME = {
[PRIVATE_COLUMN_KEY.TAG_NAME]: gettext('Tag'),
[PRIVATE_COLUMN_KEY.PARENT_LINKS]: gettext('Parent tags'),
- [PRIVATE_COLUMN_KEY.SUB_LINKS]: gettext('Sub tags count'),
+ [PRIVATE_COLUMN_KEY.SUB_LINKS]: gettext('Child tags count'),
[PRIVATE_COLUMN_KEY.TAG_FILE_LINKS]: gettext('File count'),
};
diff --git a/frontend/src/tag/views/all-tags/tags-table/context-menu-options.js b/frontend/src/tag/views/all-tags/tags-table/context-menu-options.js
index 32e71828a8..981058d735 100644
--- a/frontend/src/tag/views/all-tags/tags-table/context-menu-options.js
+++ b/frontend/src/tag/views/all-tags/tags-table/context-menu-options.js
@@ -12,6 +12,7 @@ const OPERATION = {
SET_SUB_TAGS: 'set_sub_tags',
DELETE_TAG: 'delete_tag',
DELETE_TAGS: 'delete_tags',
+ NEW_SUB_TAG: 'new_sub_tag',
};
export const createContextMenuOptions = ({
@@ -28,8 +29,10 @@ export const createContextMenuOptions = ({
recordGetterByIndex,
recordGetterById,
onDeleteTags,
+ onNewSubTag,
}) => {
const canDeleteTag = context.checkCanDeleteTag();
+ const canAddTag = context.canAddTag();
const eventBus = EventBus.getInstance();
const onClickMenuItem = (event, option) => {
@@ -48,7 +51,10 @@ export const createContextMenuOptions = ({
onDeleteTags(option.tagsIds);
break;
}
-
+ case OPERATION.NEW_SUB_TAG: {
+ onNewSubTag(option.parentTagId);
+ break;
+ }
default: {
break;
}
@@ -118,7 +124,8 @@ export const createContextMenuOptions = ({
const tag = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex });
const column = getColumnByIndex(idx, columns);
if (!tag || !tag._id || !column) return options;
- if (checkIsNameColumn(column)) {
+ const isNameColumn = checkIsNameColumn(column);
+ if (isNameColumn) {
options.push({
label: gettext('Edit tag'),
value: OPERATION.EDIT_TAG,
@@ -127,7 +134,7 @@ export const createContextMenuOptions = ({
if (column.key === PRIVATE_COLUMN_KEY.SUB_LINKS) {
options.push({
- label: gettext('Set sub tags'),
+ label: gettext('Set child tags'),
value: OPERATION.SET_SUB_TAGS,
});
}
@@ -140,6 +147,13 @@ export const createContextMenuOptions = ({
});
}
+ if (isNameColumn && canAddTag) {
+ options.push({
+ label: gettext('New child tag'),
+ value: OPERATION.NEW_SUB_TAG,
+ parentTagId: tag._id,
+ });
+ }
return options;
};
diff --git a/frontend/src/tag/views/all-tags/tags-table/editors/sub-tags.js b/frontend/src/tag/views/all-tags/tags-table/editors/child-tags.js
similarity index 72%
rename from frontend/src/tag/views/all-tags/tags-table/editors/sub-tags.js
rename to frontend/src/tag/views/all-tags/tags-table/editors/child-tags.js
index 9ec6648d48..ebe051a654 100644
--- a/frontend/src/tag/views/all-tags/tags-table/editors/sub-tags.js
+++ b/frontend/src/tag/views/all-tags/tags-table/editors/child-tags.js
@@ -2,17 +2,17 @@ import React, { forwardRef, useCallback, useMemo } from 'react';
import TagsEditor from '../../../../../components/sf-table/editors/tags-editor';
import { useTags } from '../../../../hooks';
import { getRowById } from '../../../../../components/sf-table/utils/table';
-import { getSubLinks } from '../../../../utils/cell';
+import { getChildLinks } from '../../../../utils/cell';
-const SubTagsEditor = forwardRef(({ editingRowId, column, addTagLinks, deleteTagLinks, ...editorProps }, ref) => {
+const ChildTagsEditor = forwardRef(({ editingRowId, column, addTagLinks, deleteTagLinks, ...editorProps }, ref) => {
const { tagsData } = useTags();
const tag = useMemo(() => {
return getRowById(tagsData, editingRowId);
}, [tagsData, editingRowId]);
- const subTags = useMemo(() => {
- return getSubLinks(tag);
+ const childTags = useMemo(() => {
+ return getChildLinks(tag);
}, [tag]);
const selectTag = useCallback((tagId, recordId) => {
@@ -24,12 +24,12 @@ const SubTagsEditor = forwardRef(({ editingRowId, column, addTagLinks, deleteTag
}, [column, deleteTagLinks]);
return (
-
+
{
switch (column.key) {
@@ -15,7 +15,7 @@ export const createColumnEditor = ({ column, otherProps }) => {
}
case PRIVATE_COLUMN_KEY.SUB_LINKS: {
const { addTagLinks, deleteTagLinks } = otherProps;
- return ;
+ return ;
}
default: {
return null;
diff --git a/frontend/src/tag/views/all-tags/tags-table/formatter/child-tags.js b/frontend/src/tag/views/all-tags/tags-table/formatter/child-tags.js
new file mode 100644
index 0000000000..d27cbd527c
--- /dev/null
+++ b/frontend/src/tag/views/all-tags/tags-table/formatter/child-tags.js
@@ -0,0 +1,18 @@
+import React, { useMemo } from 'react';
+import { NumberFormatter } from '@seafile/sf-metadata-ui-component';
+
+const ChildTagsFormatter = ({ record, column }) => {
+
+ const childTagsLinksCount = useMemo(() => {
+ const subTagLinks = record[column.key];
+ return Array.isArray(subTagLinks) ? subTagLinks.length : 0;
+ }, [record, column]);
+
+ return (
+
+
+
+ );
+};
+
+export default ChildTagsFormatter;
diff --git a/frontend/src/tag/views/all-tags/tags-table/formatter/formatter-factory.js b/frontend/src/tag/views/all-tags/tags-table/formatter/formatter-factory.js
index f16fde96d2..8a58374d96 100644
--- a/frontend/src/tag/views/all-tags/tags-table/formatter/formatter-factory.js
+++ b/frontend/src/tag/views/all-tags/tags-table/formatter/formatter-factory.js
@@ -1,6 +1,6 @@
import ParentTagsFormatter from './parent-tags';
import TagNameFormatter from './tag-name';
-import SubTagsFormatter from './sub-tags';
+import ChildTagsFormatter from './child-tags';
import TagFilesFormatter from './tag-files';
import { PRIVATE_COLUMN_KEY } from '../../../../constants';
@@ -13,7 +13,7 @@ export const createColumnFormatter = ({ column }) => {
return ;
}
case PRIVATE_COLUMN_KEY.SUB_LINKS: {
- return ;
+ return ;
}
case PRIVATE_COLUMN_KEY.TAG_FILE_LINKS: {
return ;
diff --git a/frontend/src/tag/views/all-tags/tags-table/formatter/sub-tags.js b/frontend/src/tag/views/all-tags/tags-table/formatter/sub-tags.js
deleted file mode 100644
index 9fe8bac52f..0000000000
--- a/frontend/src/tag/views/all-tags/tags-table/formatter/sub-tags.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React, { useMemo } from 'react';
-import { NumberFormatter } from '@seafile/sf-metadata-ui-component';
-
-const SubTagsFormatter = ({ record, column }) => {
-
- const subTagLinksCount = useMemo(() => {
- const subTagLinks = record[column.key];
- return Array.isArray(subTagLinks) ? subTagLinks.length : 0;
- }, [record, column]);
-
- return (
-
-
-
- );
-};
-
-export default SubTagsFormatter;
diff --git a/frontend/src/tag/views/all-tags/tags-table/index.css b/frontend/src/tag/views/all-tags/tags-table/index.css
index ed0e86cfb0..6db83dfeba 100644
--- a/frontend/src/tag/views/all-tags/tags-table/index.css
+++ b/frontend/src/tag/views/all-tags/tags-table/index.css
@@ -2,7 +2,7 @@
width: 100%;
}
-.sf-table-sub-tags-formatter,
+.sf-table-child-tags-formatter,
.sf-table-tag-files-formatter {
text-align: right;
}
diff --git a/frontend/src/tag/views/all-tags/tags-table/index.js b/frontend/src/tag/views/all-tags/tags-table/index.js
index 1890668175..ad4f8576c0 100644
--- a/frontend/src/tag/views/all-tags/tags-table/index.js
+++ b/frontend/src/tag/views/all-tags/tags-table/index.js
@@ -1,6 +1,7 @@
-import React, { useCallback, useMemo } from 'react';
+import React, { useCallback, useMemo, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import SFTable from '../../../../components/sf-table';
+import EditTagDialog from '../../../components/dialog/edit-tag-dialog';
import CellOperationBtn from './cell-operation';
import { createTableColumns } from './columns-factory';
import { createContextMenuOptions } from './context-menu-options';
@@ -34,15 +35,33 @@ const TagsTable = ({
setDisplayTag,
loadMore,
}) => {
- const { tagsData, updateTag, deleteTags, addTagLinks, deleteTagLinks } = useTags();
+ const { tagsData, updateTag, deleteTags, addTagLinks, deleteTagLinks, addChildTag } = useTags();
- const handleDeleteTags = useCallback((tagsIds) => {
+ const [isShowNewSubTagDialog, setIsShowNewSubTagDialog] = useState(false);
+
+ const parentTagIdRef = useRef(null);
+
+ const onDeleteTags = useCallback((tagsIds) => {
deleteTags(tagsIds);
const eventBus = EventBus.getInstance();
eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
}, [deleteTags]);
+ const onNewSubTag = useCallback((parentTagId) => {
+ parentTagIdRef.current = parentTagId;
+ setIsShowNewSubTagDialog(true);
+ }, []);
+
+ const closeNewSubTagDialog = useCallback(() => {
+ parentTagIdRef.current = null;
+ setIsShowNewSubTagDialog(false);
+ }, []);
+
+ const handelAddChildTag = useCallback((tagData, callback) => {
+ addChildTag(tagData, parentTagIdRef.current, callback);
+ }, [addChildTag]);
+
const table = useMemo(() => {
if (!tagsData) {
return {
@@ -128,9 +147,10 @@ const TagsTable = ({
return createContextMenuOptions({
...tableProps,
context,
- onDeleteTags: handleDeleteTags,
+ onDeleteTags,
+ onNewSubTag,
});
- }, [context, handleDeleteTags]);
+ }, [context, onDeleteTags, onNewSubTag]);
const checkCanModifyTag = useCallback((tag) => {
return context.canModifyTag(tag);
@@ -175,6 +195,9 @@ const TagsTable = ({
modifyColumnWidth={modifyColumnWidth}
loadMore={loadMore}
/>
+ {isShowNewSubTagDialog && (
+
+ )}
>
);
};