1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-17 15:53:28 +00:00

feat: tags keydown support(ctrl+c, delete, drag) (#7107)

* feat: tags keydown support(ctrl+c, delete, drag)

* feat: optimize code

* feat: optimize code

---------

Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
杨国璇
2024-11-26 17:45:58 +08:00
committed by GitHub
parent 4aa91aebc6
commit b083ecad29
22 changed files with 192 additions and 168 deletions

View File

@@ -58,7 +58,7 @@ class PopupEditorContainer extends React.Component {
};
createEditor = () => {
const { column, record, height, onPressTab, editorPosition, columns, modifyColumnData, addFileTags, updateFileTags } = this.props;
const { column, record, height, onPressTab, editorPosition, columns, modifyColumnData, updateFileTags } = this.props;
const readOnly = !canEditCell(column, record, true) || NOT_SUPPORT_EDITOR_COLUMN_TYPES.includes(column.type);
const value = this.getInitialValue(readOnly);
@@ -81,7 +81,6 @@ class PopupEditorContainer extends React.Component {
column,
readOnly,
onPressTab,
addFileTags,
updateFileTags,
};

View File

@@ -22,7 +22,6 @@ const TagsEditor = forwardRef(({
value: oldValue,
editorPosition = { left: 0, top: 0 },
onPressTab,
addFileTags,
updateFileTags,
}, ref) => {
const { tagsData, addTag } = useTags();
@@ -68,12 +67,8 @@ const TagsEditor = forwardRef(({
}
setValue(newValue);
const recordId = getRecordIdFromRecord(record);
if (value.length === 0) {
addFileTags(recordId, newValue, value);
} else {
updateFileTags(recordId, newValue, value);
}
}, [value, record, addFileTags, updateFileTags]);
updateFileTags([{ record_id: recordId, tags: newValue, old_tags: value }]);
}, [value, record, updateFileTags]);
const onDeleteTag = useCallback((tagId) => {
const newValue = value.slice(0);
@@ -83,7 +78,7 @@ const TagsEditor = forwardRef(({
}
setValue(newValue);
const recordId = getRecordIdFromRecord(record);
updateFileTags(recordId, newValue, value);
updateFileTags([{ record_id: recordId, tags: newValue, old_tags: value }]);
}, [value, record, updateFileTags]);
const onMenuMouseEnter = useCallback((highlightIndex) => {
@@ -103,21 +98,15 @@ const TagsEditor = forwardRef(({
success_callback: (operation) => {
const tags = operation.tags?.map(tag => getTagId(tag));
const recordId = getRecordIdFromRecord(record);
let newValue = [];
if (value.length === 0) {
newValue = tags;
addFileTags(recordId, newValue, value);
} else {
newValue = [...value, ...tags];
updateFileTags(recordId, newValue, value);
}
const newValue = [...value, ...tags];
updateFileTags([{ record_id: recordId, tags: newValue, old_tags: value }]);
setValue(newValue);
},
fail_callback: () => {
},
});
}, [value, searchValue, record, addTag, addFileTags, updateFileTags]);
}, [value, searchValue, record, addTag, updateFileTags]);
const getMaxItemNum = useCallback(() => {
let selectContainerStyle = getComputedStyle(editorContainerRef.current, null);
@@ -277,6 +266,7 @@ TagsEditor.propTypes = {
value: PropTypes.array,
editorPosition: PropTypes.object,
onPressTab: PropTypes.func,
updateFileTags: PropTypes.func,
};
export default TagsEditor;

View File

@@ -259,14 +259,9 @@ class Context {
};
// file tag
addFileTags = (recordId, tagIds) => {
updateFileTags = (data) => {
const repoID = this.settings['repoID'];
return this.tagsAPI.addFileTags(repoID, recordId, tagIds);
};
updateFileTags = (recordId, tagIds) => {
const repoID = this.settings['repoID'];
return this.tagsAPI.updateFileTags(repoID, recordId, tagIds);
return this.tagsAPI.updateFileTags(repoID, data);
};
}

View File

@@ -569,18 +569,10 @@ class Store {
};
// tag
addFileTags = (recordId, tagIds) => {
const type = OPERATION_TYPE.ADD_FILE_TAGS;
const operation = this.createOperation({
type, repo_id: this.repoId, record_id: recordId, tag_ids: tagIds
});
this.applyOperation(operation);
};
updateFileTags = (recordId, tagIds) => {
updateFileTags = (data) => {
const type = OPERATION_TYPE.UPDATE_FILE_TAGS;
const operation = this.createOperation({
type, repo_id: this.repoId, record_id: recordId, tag_ids: tagIds
type, repo_id: this.repoId, file_tags_data: data
});
this.applyOperation(operation);
};

View File

@@ -257,15 +257,20 @@ export default function apply(data, operation) {
}
// tags
case OPERATION_TYPE.ADD_FILE_TAGS:
case OPERATION_TYPE.UPDATE_FILE_TAGS: {
const { record_id, tag_ids } = operation;
const { file_tags_data: filesTagsData } = operation;
const { rows } = data;
let updateMap = {};
Array.isArray(filesTagsData) && filesTagsData.forEach(fileTags => {
const { record_id, tags } = fileTags;
const value = tags ? tags.map(tagId => ({ row_id: tagId })) : [];
updateMap[record_id] = value;
});
let updatedRows = [...rows];
rows.forEach((row, index) => {
const { _id: rowId } = row;
if (rowId === record_id) {
const updatedRow = Object.assign({}, row, { [PRIVATE_COLUMN_KEY.TAGS]: tag_ids ? tag_ids.map(item => ({ row_id: item })) : [] });
if (updateMap[rowId]) {
const updatedRow = Object.assign({}, row, { [PRIVATE_COLUMN_KEY.TAGS]: updateMap[rowId] });
updatedRows[index] = updatedRow;
data.id_row_map[rowId] = updatedRow;
}

View File

@@ -25,7 +25,6 @@ export const OPERATION_TYPE = {
DELETE_PEOPLE_PHOTOS: 'delete_people_photos',
// tag
ADD_FILE_TAGS: 'add_file_tags',
UPDATE_FILE_TAGS: 'update_file_tags',
};
@@ -59,8 +58,7 @@ export const OPERATION_ATTRIBUTES = {
[OPERATION_TYPE.DELETE_PEOPLE_PHOTOS]: ['repo_id', 'people_id', 'deleted_photos'],
[OPERATION_TYPE.MODIFY_SETTINGS]: ['repo_id', 'view_id', 'settings'],
[OPERATION_TYPE.MODIFY_LOCAL_RECORD]: ['repo_id', 'row_id', 'updates'],
[OPERATION_TYPE.ADD_FILE_TAGS]: ['repo_id', 'record_id', 'tag_ids'],
[OPERATION_TYPE.UPDATE_FILE_TAGS]: ['repo_id', 'record_id', 'tag_ids'],
[OPERATION_TYPE.UPDATE_FILE_TAGS]: ['repo_id', 'file_tags_data'],
};
export const UNDO_OPERATION_TYPE = [

View File

@@ -195,22 +195,18 @@ class ServerOperator {
}
// tags
case OPERATION_TYPE.ADD_FILE_TAGS: {
const { record_id, tag_ids } = operation;
window.sfMetadataContext.addFileTags(record_id, tag_ids).then(res => {
callback({ operation });
}).catch(error => {
callback({ error: gettext('Failed to modify people name') });
});
break;
}
case OPERATION_TYPE.UPDATE_FILE_TAGS: {
const { record_id, tag_ids } = operation;
window.sfMetadataContext.updateFileTags(record_id, tag_ids).then(res => {
callback({ operation });
const { file_tags_data } = operation;
let valid_files_tags_data = [];
file_tags_data.forEach(item => {
const { record_id, tags } = item;
valid_files_tags_data.push({ record_id, tags });
});
window.sfMetadataContext.updateFileTags(valid_files_tags_data).then(res => {
const { success: success_record_ids, fail: fail_record_ids } = res.data;
callback({ operation, success_record_ids, fail_record_ids });
}).catch(error => {
callback({ error: gettext('Failed to modify people name') });
callback({ error: gettext('Failed to modify tags') });
});
break;
}

View File

@@ -47,13 +47,13 @@ function convertedToRecordData(originRecordData, keyColumnMap, excludesColumnTyp
return recordData;
}
export const getClientCellValueDisplayString = (record, column, { collaborators = [] } = {}) => {
export const getClientCellValueDisplayString = (record, column, { collaborators = [], tagsData = {} } = {}) => {
const cellValue = getCellValueByColumn(record, column);
const { type } = column;
if (type === CellType.CTIME || type === CellType.MTIME) {
return getAutoTimeDisplayString(cellValue);
}
return getCellValueDisplayString(record, column, { collaborators });
return getCellValueDisplayString(record, column, { collaborators, tagsData });
};
export const getFormatRecordData = (columns, recordData) => {

View File

@@ -28,3 +28,6 @@ export {
getGeolocationDisplayString,
getGeolocationByGranularity,
} from './geolocation';
export {
getTagsDisplayString,
} from './tag';

View File

@@ -0,0 +1,8 @@
import { getTagName } from '../../../../tag/utils';
import { getRowById } from '../../table';
export const getTagsDisplayString = (tagsData, cellValue) => {
if (!tagsData) return '';
if (!Array.isArray(cellValue) || cellValue.length === 0) return '';
return cellValue.map(item => getRowById(tagsData, item.row_id)).map(tag => getTagName(tag)).join(', ');
};

View File

@@ -4,10 +4,10 @@ import { getCellValueByColumn } from './core';
import { getColumnOptions } from '../column';
import {
getDateDisplayString, getNumberDisplayString, getLongtextDisplayString, getOptionName, getCollaboratorsName, getColumnOptionNamesByIds,
getGeolocationDisplayString, getColumnOptionIdsByNames,
getGeolocationDisplayString, getColumnOptionIdsByNames, getTagsDisplayString,
} from './column';
export const getCellValueDisplayString = (row, column, { collaborators = [] } = {}) => {
export const getCellValueDisplayString = (row, column, { collaborators = [], tagsData } = {}) => {
if (!row) return '';
const { type, data } = column;
const cellValue = getCellValueByColumn(row, column);
@@ -47,6 +47,9 @@ export const getCellValueDisplayString = (row, column, { collaborators = [] } =
case CellType.GEOLOCATION: {
return getGeolocationDisplayString(cellValue, data, { isBaiduMap: true, hyphen: ' ' });
}
case CellType.TAGS: {
return getTagsDisplayString(tagsData, cellValue);
}
default: {
if (cellValue || typeof cellValue === 'boolean') {
return String(cellValue);

View File

@@ -184,12 +184,8 @@ const Table = () => {
store.modifyColumnOrder(sourceColumnKey, targetColumnKey);
}, [store]);
const addFileTags = useCallback((recordId, tagIds) => {
store.addFileTags(recordId, tagIds);
}, [store]);
const updateFileTags = useCallback((recordId, tagIds) => {
store.updateFileTags(recordId, tagIds);
const updateFileTags = useCallback((data) => {
store.updateFileTags(data);
}, [store]);
const insertColumn = useCallback((name, type, { key, data }) => {
@@ -250,7 +246,6 @@ const Table = () => {
modifyColumnData={modifyColumnData}
modifyColumnWidth={modifyColumnWidth}
modifyColumnOrder={modifyColumnOrder}
addFileTags={addFileTags}
updateFileTags={updateFileTags}
/>
</div>

View File

@@ -11,7 +11,7 @@ import { gettext } from '../../../../../utils/constants';
import { KeyCodes } from '../../../../../constants';
import { Utils } from '../../../../../utils/utils';
import { isEmptyObject } from '../../../../utils/common';
import { getCellValueByColumn } from '../../../../utils/cell';
import { getCellValueByColumn, getTagsFromRecord, isValidCellValue } from '../../../../utils/cell';
import { isCtrlKeyHeldDown, isKeyPrintable } from '../../../../utils/keyboard-utils';
import { getFormatRecordData } from '../../../../utils/cell/cell-format-utils';
import {
@@ -508,27 +508,28 @@ class InteractionMasks extends React.Component {
let idOriginalRecordUpdates = {}; // row's id to modified original records data: { [row_id]: { [column.key: null] } }
let idOldRecordData = {}; // row's id to old records data: { [row_id]: { [column.name: xxx] } }
let idOriginalOldRecordData = {}; // row's id to old original records data: { [row_id]: { [column.key: xxx] } }
let idRowLinkItems = {}; // row's id to modified links: { [row_id]: { [column.key]: null } }
let idOldRowLinkItems = {}; // row's id to old links: { [row_id]: { [column.key]: [{ row_id: xxx, display_value: 'xxx' }] } }
let tagsUpdate = [];
editableRecords.forEach(record => {
const { _id } = record;
let originalUpdate = {};
let originalOldRecordData = {};
let linkItem = {};
let oldLinkItem = {};
let hasTagsColumn = false;
editableColumns.forEach(column => {
const { key } = column;
const { key, type } = column;
const cellVal = getCellValueByColumn(record, column);
if (cellVal || cellVal === 0 || (Array.isArray(cellVal) && cellVal.length > 0)) {
originalOldRecordData[key] = cellVal;
originalUpdate[key] = null;
if (isValidCellValue(cellVal)) {
if (type === CellType.TAGS) {
hasTagsColumn = true;
} else {
originalOldRecordData[key] = cellVal;
originalUpdate[key] = null;
}
}
});
// links data
if (Object.keys(linkItem).length > 0) {
idRowLinkItems[_id] = linkItem;
idOldRowLinkItems[_id] = oldLinkItem;
// tags
if (hasTagsColumn) {
tagsUpdate.push({ record_id: _id, tags: [], old_tags: getTagsFromRecord(record) });
}
if (Object.keys(originalUpdate).length > 0) {
@@ -542,6 +543,10 @@ class InteractionMasks extends React.Component {
}
});
if (tagsUpdate.length > 0) {
this.props.updateFileTags(tagsUpdate);
}
if (updateRecordIds.length > 0) {
const isCopyPaste = true;
this.props.updateRecords({
@@ -1099,7 +1104,6 @@ class InteractionMasks extends React.Component {
onCommit={this.onCommit}
onCommitCancel={this.onCommitCancel}
modifyColumnData={this.props.modifyColumnData}
addFileTags={this.props.addFileTags}
updateFileTags={this.props.updateFileTags}
editorPosition={editorPosition}
{...{

View File

@@ -7,11 +7,22 @@ import { GROUP_VIEW_OFFSET } from '../../../constants';
import './index.css';
const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, recordGetterByIndex, recordGetterById, insertColumn, modifyColumnData, ...props }) => {
const TableMain = ({
metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, recordGetterByIndex, recordGetterById, insertColumn,
modifyColumnData, updateFileTags,
...props
}) => {
const gridUtils = useMemo(() => {
return new GridUtils(metadata, { modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById, modifyColumnData });
}, [metadata, modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById, modifyColumnData]);
return new GridUtils(metadata, {
modifyRecord,
modifyRecords,
recordGetterByIndex,
recordGetterById,
modifyColumnData,
updateFileTags,
});
}, [metadata, modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById, modifyColumnData, updateFileTags]);
const groupbysCount = useMemo(() => {
const groupbys = metadata?.view?.groupbys || [];
@@ -70,6 +81,7 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
recordGetterByIndex={recordGetterByIndex}
modifyColumnData={modifyColumnData}
insertColumn={handelInsertColumn}
updateFileTags={updateFileTags}
{...props}
/>
</div>

View File

@@ -543,7 +543,6 @@ class RecordsBody extends Component {
getCopiedRecordsAndColumnsFromRange={this.props.getCopiedRecordsAndColumnsFromRange}
modifyColumnData={this.props.modifyColumnData}
getTableCanvasContainerRect={this.props.getTableCanvasContainerRect}
addFileTags={this.props.addFileTags}
updateFileTags={this.props.updateFileTags}
/>
<div className="sf-metadata-result-table" style={{ width: this.props.totalWidth + SEQUENCE_COLUMN_WIDTH }} ref={this.setResultRef}>

View File

@@ -905,7 +905,6 @@ class GroupBody extends Component {
getCopiedRecordsAndColumnsFromRange={this.props.getCopiedRecordsAndColumnsFromRange}
modifyColumnData={this.props.modifyColumnData}
getTableCanvasContainerRect={this.props.getTableCanvasContainerRect}
addFileTags={this.props.addFileTags}
updateFileTags={this.props.updateFileTags}
/>
<div className="sf-metadata-result-table" ref={this.setResultRef}>

View File

@@ -1,6 +1,7 @@
import {
getDateDisplayString, getNumberDisplayString, formatStringToNumber, getOptionName, getCollaboratorsName, getFloatNumber, getColumnOptionNamesByIds,
getOption, checkIsPredefinedOption, getColumnOptionNameById,
getTagsDisplayString,
} from '../../../utils/cell';
import { getColumnOptions, generatorCellOption, generatorCellOptions, isLongTextValueExceedLimit, getValidLongTextValue } from '../../../utils/column';
import { isNumber } from '../../../utils/number';
@@ -198,6 +199,11 @@ function convert2Text(cellValue, oldCellValue, fromColumn) {
const collaborators = window.sfMetadata.getCollaborators();
return getCollaboratorsName(collaborators, [cellValue]);
}
case CellType.TAGS: {
if (!Array.isArray(cellValue) || cellValue.length === 0) return null;
const tagsData = window?.sfTagsDataStore?.data || {};
return getTagsDisplayString(tagsData, cellValue);
}
default: {
return oldCellValue;
}
@@ -326,6 +332,13 @@ const convert2MultipleSelect = (copiedCellVal, pasteCellVal, copiedColumn, paste
return getColumnOptionNamesByIds(newColumn, selectedOptionIds);
};
const convert2Tags = (cellValue, oldCellValue, fromColumn, targetColumn, api) => {
const { key: copiedColumnKey } = fromColumn;
const { key: pasteColumnKey } = targetColumn;
if (copiedColumnKey === pasteColumnKey) return cellValue;
return;
};
function convertCellValue(cellValue, oldCellValue, targetColumn, fromColumn, api) {
const { type: fromColumnType, data: fromColumnData } = fromColumn;
const { type: targetColumnType, data: targetColumnData } = targetColumn;
@@ -357,6 +370,9 @@ function convertCellValue(cellValue, oldCellValue, targetColumn, fromColumn, api
case CellType.RATE: {
return convert2Rate(cellValue, oldCellValue, fromColumn, targetColumn);
}
case CellType.TAGS: {
return convert2Tags(cellValue, oldCellValue, fromColumn, targetColumn, api);
}
default: {
return oldCellValue;
}

View File

@@ -75,6 +75,7 @@ class GridUtils {
if ((copiedRecordsLen > startExpandRecordIndex)) return;
let updateTags = [];
let updateRecordIds = [];
let idRecordUpdates = {};
let idOriginalRecordUpdates = {};
@@ -115,7 +116,9 @@ class GridUtils {
const pasteCellValue = Object.prototype.hasOwnProperty.call(pasteRecord, pasteColumnName) ? getCellValueByColumn(pasteRecord, pasteColumn) : null;
const copiedCellValue = Object.prototype.hasOwnProperty.call(copiedRecord, copiedColumnName) ? getCellValueByColumn(copiedRecord, copiedColumn) : null;
const update = convertCellValue(copiedCellValue, pasteCellValue, pasteColumn, copiedColumn, this.api);
if (update === pasteCellValue) {
if (!isCellValueChanged(pasteCellValue, update, pasteColumn.type)) continue;
if (pasteColumn.type === CellType.TAGS) {
updateTags.push({ record_id: updateRecordId, tags: Array.isArray(update) ? update.map(i => i.row_id) : [], old_tags: pasteCellValue });
continue;
}
originalUpdate[pasteColumnName] = update;
@@ -133,6 +136,10 @@ class GridUtils {
}
}
if (updateTags.length > 0) {
this.api.updateFileTags(updateTags);
}
if (updateRecordIds.length === 0) return;
this.api.modifyRecords(updateRecordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData, isCopyPaste);
}
@@ -170,6 +177,7 @@ class GridUtils {
}
getUpdateDraggedRecords(draggedRange, shownColumns, rows, idRowMap, groupMetrics) {
let tagsUpdate = [];
let rowIds = [];
let updatedOriginalRows = {};
let oldOriginalRows = {};
@@ -211,21 +219,36 @@ class GridUtils {
const value = draggedRangeMatrix[j - startColumnIdx][idx];
const rule = rules[cellKey];
const fillingValue = rule({ n: fillingIndex - 1, value });
if (isCellValueChanged(fillingValue, dragRow[columnName], type)) {
updatedOriginalRows[dragRowId] = Object.assign({}, updatedOriginalRows[dragRowId], { [columnName]: fillingValue });
oldOriginalRows[dragRowId] = Object.assign({}, oldOriginalRows[dragRowId], { [columnName]: dragRow[columnName] });
const update = updatedOriginalRows[dragRowId];
const oldUpdate = oldOriginalRows[dragRowId];
const oldValue = dragRow[columnName];
if (isCellValueChanged(fillingValue, oldValue, type)) {
if (type === CellType.TAGS) {
tagsUpdate.push({ record_id: dragRowId, tags: fillingValue || [], old_tags: oldValue });
} else {
updatedOriginalRows[dragRowId] = Object.assign({}, updatedOriginalRows[dragRowId], { [columnName]: fillingValue });
oldOriginalRows[dragRowId] = Object.assign({}, oldOriginalRows[dragRowId], { [columnName]: oldValue });
const update = updatedOriginalRows[dragRowId];
const oldUpdate = oldOriginalRows[dragRowId];
updatedRows[dragRowId] = Object.assign({}, updatedRows[dragRowId], update);
oldRows[dragRowId] = Object.assign({}, oldRows[dragRowId], oldUpdate);
updatedRows[dragRowId] = Object.assign({}, updatedRows[dragRowId], update);
oldRows[dragRowId] = Object.assign({}, oldRows[dragRowId], oldUpdate);
}
}
}
}
currentGroupRowIndex++;
}
return { recordIds: rowIds, idOriginalRecordUpdates: updatedOriginalRows, idRecordUpdates: updatedRows, idOriginalOldRecordData: oldOriginalRows, idOldRecordData: oldRows };
if (tagsUpdate.length > 0) {
this.api.updateFileTags(tagsUpdate);
}
return {
recordIds: rowIds,
idOriginalRecordUpdates: updatedOriginalRows,
idRecordUpdates: updatedRows,
idOriginalOldRecordData: oldOriginalRows,
idOldRecordData: oldRows
};
}
getDraggedRangeMatrix(columns, draggedRange, rows, groupMetrics, idRowMap) {
@@ -299,6 +322,14 @@ class GridUtils {
ruleMatrixItem = this._getRatingLeastSquares(valueList, data);
break;
}
case CellType.TAGS: {
ruleMatrixItem = ({ value }) => {
if (!value) return [];
if (!Array.isArray(value) || value.length === 0) return [];
return value.map(item => item.row_id);
};
break;
}
default: {
ruleMatrixItem = NORMAL_RULE;
break;

View File

@@ -65,12 +65,13 @@ function getCopiedTextFromSelectedCells(copiedRange, tableData, isGroupView, rec
function getCopiedText(records, columns) {
const collaborators = window.sfMetadata.getCollaborators();
const tagsData = window?.sfTagsDataStore?.data || {};
const lastRecordIndex = records.length - 1;
const lastColumnIndex = columns.length - 1;
let copiedText = '';
records.forEach((record, recordIndex) => {
columns.forEach((column, columnIndex) => {
copiedText += (record && getClientCellValueDisplayString(record, column, { collaborators })) || '';
copiedText += (record && getClientCellValueDisplayString(record, column, { collaborators, tagsData })) || '';
if (columnIndex < lastColumnIndex) {
copiedText += '\t';
}

View File

@@ -93,21 +93,11 @@ class TagsManagerAPI {
return this.req.get(url);
};
// file tag
addFileTags = (repoID, recordId, tagIds) => {
// file tags
updateFileTags = (repoID, data) => {
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/file-tags/';
const params = {
record_id: recordId,
tags: tagIds,
};
return this.req.post(url, params);
};
updateFileTags = (repoID, recordId, tagIds) => {
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/file-tags/';
const params = {
record_id: recordId,
tags: tagIds,
file_tags_data: data,
};
return this.req.put(url, params);
};

View File

@@ -1,6 +1,7 @@
.tag-list-title .sf-metadata-tags-formatter .sf-metadata-tag-formatter {
height: 16px;
width: 16px;
top: 0;
}
.tag-list-title .sf-metadata-tags-formatter .sf-metadata-tag-formatter:last-child {
@@ -10,3 +11,8 @@
.sf-metadata-tags-main .table-container td.name a {
word-break: break-word;
}
.sf-metadata-tags-main .tag-list-title .sf-metadata-tags-formatter {
width: fit-content;
max-width: 100%;
}

View File

@@ -1556,61 +1556,10 @@ class MetadataFileTags(APIView):
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def post(self, request, repo_id):
record_id = request.data.get('record_id')
if not record_id:
error_msg = 'record_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
tags = request.data.get('tags', [])
if not tags:
error_msg = 'tags invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
metadata = RepoMetadata.objects.filter(repo_id=repo_id).first()
if not metadata or not metadata.enabled:
error_msg = f'The metadata module is disabled for repo {repo_id}.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo = seafile_api.get_repo(repo_id)
if not repo:
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if not can_read_metadata(request, repo_id):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
from seafevents.repo_metadata.constants import TAGS_TABLE, METADATA_TABLE
try:
metadata = metadata_server_api.get_metadata()
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
tables = metadata.get('tables', [])
tags_table_id = [table['id'] for table in tables if table['name'] == TAGS_TABLE.name]
tags_table_id = tags_table_id[0] if tags_table_id else None
if not tags_table_id:
return api_error(status.HTTP_404_NOT_FOUND, 'tags not be used')
try:
metadata_server_api.insert_link(repo_id, TAGS_TABLE.link_id, METADATA_TABLE.id, { record_id: tags })
except Exception as e:
logger.exception(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({'success': True})
def put(self, request, repo_id):
record_id = request.data.get('record_id')
if not record_id:
error_msg = 'record_id invalid.'
file_tags_data = request.data.get('file_tags_data')
if not file_tags_data:
error_msg = 'file_tags_data invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
tags = request.data.get('tags', [])
@@ -1645,15 +1594,48 @@ class MetadataFileTags(APIView):
if not tags_table_id:
return api_error(status.HTTP_404_NOT_FOUND, 'tags not be used')
file_record_ids = []
file_tags_map = {}
for file_tags in file_tags_data:
record_id = file_tags.get('record_id', '')
tags = file_tags.get('tags', [])
if record_id:
file_record_ids.append(record_id)
file_tags_map[record_id] = tags
if not file_record_ids:
return Response({'success': [], 'fail': []})
file_record_ids_str = ', '.join(["'%s'" % id for id in file_record_ids])
current_result_sql = f'SELECT `{METADATA_TABLE.columns.tags.key}`, `{METADATA_TABLE.columns.id.key}` FROM `{METADATA_TABLE.name}` WHERE `{METADATA_TABLE.columns.id.key}` IN ({file_record_ids_str})'
try:
metadata_server_api.update_link(repo_id, TAGS_TABLE.link_id, METADATA_TABLE.id, { record_id: tags })
current_result_query = metadata_server_api.query_rows(current_result_sql)
except Exception as e:
logger.exception(e)
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({'success': True})
success_records = []
failed_records = []
files_records = current_result_query.get('results', [])
if not files_records:
return api_error(status.HTTP_404_NOT_FOUND, 'files not found')
for file_record in files_records:
current_tags = file_record.get(METADATA_TABLE.columns.tags.key, [])
record_id = file_record.get(METADATA_TABLE.columns.id.key)
tags = file_tags_map[record_id]
try:
if not current_tags:
metadata_server_api.insert_link(repo_id, TAGS_TABLE.link_id, METADATA_TABLE.id, { record_id: tags })
else:
metadata_server_api.update_link(repo_id, TAGS_TABLE.link_id, METADATA_TABLE.id, { record_id: tags })
success_records.append(record_id)
except Exception as e:
failed_records.append(record_id)
return Response({'success': success_records, 'fail': failed_records})
class MetadataTagFiles(APIView):