diff --git a/frontend/src/metadata/constants/index.js b/frontend/src/metadata/constants/index.js index 33edf2ff32..c590143b67 100644 --- a/frontend/src/metadata/constants/index.js +++ b/frontend/src/metadata/constants/index.js @@ -135,3 +135,9 @@ export const GALLERY_DATE_MODE = { }; export const UNCATEGORIZED = '_uncategorized'; + +export const FILE_TYPE = { + MARKDOWN: 'markdown', + SDOC: 'sdoc', + IMAGE: 'image', +}; diff --git a/frontend/src/metadata/views/kanban/boards/board/card/index.css b/frontend/src/metadata/views/kanban/boards/board/card/index.css index 6c35637e30..2bc8b3cebb 100644 --- a/frontend/src/metadata/views/kanban/boards/board/card/index.css +++ b/frontend/src/metadata/views/kanban/boards/board/card/index.css @@ -9,6 +9,7 @@ display: flex; flex-direction: column; overflow: hidden; + user-select: none; } .sf-metadata-kanban-card:hover { @@ -16,6 +17,10 @@ box-shadow: 0 0 6px #0000002e; } +.sf-metadata-kanban-card.readonly { + cursor: default; +} + .sf-metadata-kanban-card .sf-metadata-kanban-card-header { font-weight: 500; width: 100%; @@ -34,10 +39,6 @@ margin-bottom: 0; } -.sf-metadata-kanban-card .sf-metadata-kanban-card-record .file-name-formatter-wrapper { - width: fit-content; -} - .sf-metadata-kanban-card .sf-metadata-kanban-card-record .sf-metadata-kanban-card-record-name { margin-bottom: 4px; color: #666; @@ -76,7 +77,7 @@ } .sf-metadata-kanban-card .sf-metadata-special-file-name-formatter:not(.sf-metadata-image-file-formatter) { - margin-left: -2px; + transform: translateX(-2px) } .sf-metadata-kanban-card .file-name-formatter .sf-metadata-file-icon { @@ -89,6 +90,7 @@ } .sf-metadata-kanban-card .sf-metadata-ui.file-name-formatter .sf-metadata-file-name:hover { + cursor: pointer; text-decoration: underline; text-decoration-color: #212529; } diff --git a/frontend/src/metadata/views/kanban/boards/board/card/index.js b/frontend/src/metadata/views/kanban/boards/board/card/index.js index dbc9714666..02347a25d2 100644 --- a/frontend/src/metadata/views/kanban/boards/board/card/index.js +++ b/frontend/src/metadata/views/kanban/boards/board/card/index.js @@ -1,10 +1,11 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { getCellValueByColumn, isValidCellValue } from '../../../../../utils/cell'; import Formatter from '../formatter'; -import { PRIVATE_COLUMN_KEY } from '../../../../../constants'; +import { FILE_TYPE } from '../../../../../constants'; import { useMetadataView } from '../../../../../hooks/metadata-view'; +import { Utils } from '../../../../../../utils/utils'; +import { geRecordIdFromRecord, getCellValueByColumn, getFileNameFromRecord, getParentDirFromRecord, isValidCellValue } from '../../../../../utils/cell'; import './index.css'; @@ -16,7 +17,10 @@ const Card = ({ titleColumn, displayColumns, onCloseSettings, + onOpenFile, }) => { + const cardRef = useRef(null); + const { updateCurrentDirent, showDirentDetail } = useMetadataView(); const titleValue = getCellValueByColumn(record, titleColumn); @@ -24,8 +28,8 @@ const Card = ({ const handleClick = useCallback((e) => { e.preventDefault(); e.stopPropagation(); - const name = record[PRIVATE_COLUMN_KEY.FILE_NAME]; - const path = record[PRIVATE_COLUMN_KEY.PARENT_DIR]; + const name = getFileNameFromRecord(record); + const path = getParentDirFromRecord(record); updateCurrentDirent({ type: 'file', name, @@ -36,14 +40,48 @@ const Card = ({ showDirentDetail(); }, [record, updateCurrentDirent, showDirentDetail, onCloseSettings]); + const getFileType = useCallback((fileName) => { + if (!fileName) return ''; + const index = fileName.lastIndexOf('.'); + if (index === -1) return ''; + const suffix = fileName.slice(index).toLowerCase(); + if (suffix.indexOf(' ') > -1) return ''; + if (Utils.imageCheck(fileName)) return FILE_TYPE.IMAGE; + if (Utils.isMarkdownFile(fileName)) return FILE_TYPE.MARKDOWN; + if (Utils.isSdocFile(fileName)) return FILE_TYPE.SDOC; + return ''; + }, []); + const handleClickFilename = useCallback((e) => { e.preventDefault(); e.stopPropagation(); - console.log('click filename'); - }, []); + + const fileName = getFileNameFromRecord(record); + const fileType = getFileType(fileName); + const parentDir = getParentDirFromRecord(record); + const recordId = geRecordIdFromRecord(record); + onOpenFile(fileType, fileName, parentDir, recordId); + }, [record, getFileType, onOpenFile]); + + useEffect(() => { + const cardElement = cardRef.current; + if (!cardElement) return; + + const filenameElement = cardElement.querySelector('.file-name-formatter .sf-metadata-file-name'); + if (filenameElement) { + filenameElement.addEventListener('click', handleClickFilename); + } + + return () => { + if (filenameElement) { + filenameElement.removeEventListener('click', handleClickFilename); + } + }; + }, [handleClickFilename]); return (
{displayColumnName && (
{column.name}
)} -
- -
+ ); })} @@ -89,6 +125,7 @@ Card.propTypes = { titleColumn: PropTypes.object, displayColumns: PropTypes.array, onCloseSettings: PropTypes.func.isRequired, + onOpenFile: PropTypes.func.isRequired, }; export default Card; diff --git a/frontend/src/metadata/views/kanban/boards/board/index.js b/frontend/src/metadata/views/kanban/boards/board/index.js index 79679b0ec8..d4f9e4641e 100644 --- a/frontend/src/metadata/views/kanban/boards/board/index.js +++ b/frontend/src/metadata/views/kanban/boards/board/index.js @@ -24,6 +24,7 @@ const Board = ({ onFreezed, onUnFreezed, onCloseSettings, + onOpenFile, }) => { const [isDraggingOver, setDraggingOver] = useState(false); const boardName = useMemo(() => `sf_metadata_kanban_board_${board.key}`, [board]); @@ -84,6 +85,7 @@ const Board = ({ titleColumn={titleColumn} displayColumns={displayColumns} onCloseSettings={onCloseSettings} + onOpenFile={onOpenFile} /> ); if (readonly) return CardElement; @@ -113,6 +115,7 @@ Board.propTypes = { onFreezed: PropTypes.func, onUnFreezed: PropTypes.func, onCloseSettings: PropTypes.func.isRequired, + onOpenFile: PropTypes.func.isRequired, }; export default Board; diff --git a/frontend/src/metadata/views/kanban/boards/index.js b/frontend/src/metadata/views/kanban/boards/index.js index 4812ef1ae6..15d5dbab1c 100644 --- a/frontend/src/metadata/views/kanban/boards/index.js +++ b/frontend/src/metadata/views/kanban/boards/index.js @@ -3,19 +3,25 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import { useMetadataView } from '../../../hooks/metadata-view'; import { useCollaborators } from '../../../hooks'; -import { CellType, KANBAN_SETTINGS_KEYS, UNCATEGORIZED } from '../../../constants'; +import { CellType, KANBAN_SETTINGS_KEYS, UNCATEGORIZED, FILE_TYPE } from '../../../constants'; import { COLUMN_DATA_OPERATION_TYPE } from '../../../store/operations'; -import { gettext } from '../../../../utils/constants'; +import { gettext, siteRoot } from '../../../../utils/constants'; import { checkIsPredefinedOption, getCellValueByColumn, isValidCellValue, geRecordIdFromRecord } from '../../../utils/cell'; import { getColumnOptions, getColumnOriginName } from '../../../utils/column'; import AddBoard from '../add-board'; import EmptyTip from '../../../../components/empty-tip'; import Board from './board'; +import { Utils } from '../../../../utils/utils'; +import { EVENT_BUS_TYPE } from '../../../../components/common/event-bus-type'; +import ImagePreviewer from '../../../components/cell-formatter/image-previewer'; import './index.css'; +import { getRowById } from '../../../utils/table'; const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { const [haveFreezed, setHaveFreezed] = useState(false); + const [isShowImagePreviewer, setIsShowImagePreviewer] = useState(false); + const [record, setRecord] = useState(null); const { metadata, store } = useMetadataView(); const { collaborators } = useCollaborators(); @@ -166,6 +172,61 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { const isEmpty = boards.length === 0; + const generateUrl = (fileName, parentDir) => { + const repoID = window.sfMetadataContext.getSetting('repoID'); + const path = Utils.encodePath(Utils.joinPath(parentDir, fileName)); + return `${siteRoot}lib/${repoID}/file${path}`; + }; + + const openMarkdown = (name, parentDir) => { + window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.OPEN_MARKDOWN_DIALOG, parentDir, name); + }; + + const openByNewWindow = (fileType, fileName, parentDir) => { + if (!fileType) { + const url = generateUrl(fileName, parentDir); + window.open(url); + } else { + let pathname = window.location.pathname; + if (pathname.endsWith('/')) { + pathname = pathname.slice(0, -1); + } + window.open(window.location.origin + pathname + Utils.encodePath(Utils.joinPath(parentDir, fileName))); + } + }; + + const openSdoc = (fileName, parentDir) => { + const url = generateUrl(fileName, parentDir); + window.open(url); + }; + + const openFile = (type, fileName, parentDir, recordId = null) => { + switch (type) { + case FILE_TYPE.MARKDOWN: { + openMarkdown(fileName, parentDir); + break; + } + case FILE_TYPE.SDOC: { + openSdoc(fileName, parentDir); + break; + } + case FILE_TYPE.IMAGE: { + const record = getRowById(metadata, recordId); + setRecord(record); + setIsShowImagePreviewer(true); + break; + } + default: { + openByNewWindow(type, fileName, parentDir); + break; + } + } + }; + + const closeImagePreviewer = () => { + setIsShowImagePreviewer(false); + }; + return (
{ onFreezed={onFreezed} onUnFreezed={onUnFreezed} onCloseSettings={onCloseSettings} + onOpenFile={openFile} /> ); })} @@ -201,6 +263,7 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { )} {!readonly && ()}
+ {isShowImagePreviewer && ()} ); };