Skip to content
2 changes: 2 additions & 0 deletions datahub-web-react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { GlobalStyles } from '@components/components/GlobalStyles';

import { Routes } from '@app/Routes';
import { isLoggedInVar } from '@app/auth/checkAuthStatus';
import { FilesUploadingDownloadingLatencyTracker } from '@app/shared/FilesUploadingDownloadingLatencyTracker';
import { ErrorCodes } from '@app/shared/constants';
import { PageRoutes } from '@conf/Global';
import CustomThemeProvider from '@src/CustomThemeProvider';
Expand Down Expand Up @@ -88,6 +89,7 @@ export const InnerApp: React.VFC = () => {
<HelmetProvider>
<CustomThemeProvider>
<GlobalStyles />
<FilesUploadingDownloadingLatencyTracker />

<Helmet>
<title>{useCustomTheme().theme?.content?.title}</title>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
CodeExtension,
DropCursorExtension,
FontSizeExtension,
GapCursorExtension,
HardBreakExtension,
HeadingExtension,
HistoryExtension,
Expand Down Expand Up @@ -37,6 +38,7 @@ import { FloatingToolbar } from '@components/components/Editor/toolbar/FloatingT
import { TableCellMenu } from '@components/components/Editor/toolbar/TableCellMenu';
import { Toolbar } from '@components/components/Editor/toolbar/Toolbar';
import { EditorProps } from '@components/components/Editor/types';
import { colors } from '@components/theme';

import { notEmpty } from '@app/entityV2/shared/utils';

Expand Down Expand Up @@ -66,7 +68,10 @@ export const Editor = forwardRef((props: EditorProps, ref) => {
new CodeBlockExtension({ syntaxTheme: 'base16_ateliersulphurpool_light' }),
new CodeExtension(),
new DataHubMentionsExtension({}),
new DropCursorExtension({}),
new DropCursorExtension({
color: colors.primary[100],
width: 2,
}),
new HardBreakExtension(),
new HeadingExtension({}),
new HistoryExtension({}),
Expand All @@ -78,6 +83,7 @@ export const Editor = forwardRef((props: EditorProps, ref) => {
onFileUploadSucceeded,
onFileDownloadView,
}),
new GapCursorExtension(), // required to allow cursor placement next to non-editable inline elements
new ImageExtension({ enableResizing: !readOnly }),
new ItalicExtension(),
new LinkExtension({ autoLink: true, defaultTarget: '_blank' }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const EditorContainer = styled.div<{ $readOnly?: boolean; $hideBorder?: b
flex: 1 1 100%;
display: flex;
flex-direction: column;
width: 100%;
}

.remirror-editor.ProseMirror {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
}

createTags() {
return [ExtensionTag.Block, ExtensionTag.Behavior, ExtensionTag.FormattingNode];
return [ExtensionTag.InlineNode, ExtensionTag.Behavior];
}

get defaultPriority() {
Expand All @@ -74,7 +74,13 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
props: {
handleDOMEvents: {
drop: (view: EditorView, event: DragEvent) => {
return this.handleDrop(view, event);
const data = event.dataTransfer;
if (data && data.files && data.files.length > 0) {
// External file drop
return this.handleDrop(view, event);
}
// Moving nodes internally
return false;
},
dragover: (view: EditorView, event: DragEvent) => {
if (event.dataTransfer?.types.includes('Files')) {
Expand All @@ -97,6 +103,23 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
dragleave: (_view: EditorView, _event: DragEvent) => {
return false;
},
dragstart: (view: EditorView, event: DragEvent) => {
const pos = view.posAtCoords({ left: event.clientX, top: event.clientY });
if (!pos) return false;

const node = view.state.doc.nodeAt(pos.pos);
if (!node || node.type !== this.type) return false;

const data = event.dataTransfer;
if (data) {
data.setData(
'application/x-prosemirror-node',
JSON.stringify({ id: node.attrs.id, type: node.type.name }),
);
data.effectAllowed = 'move';
}
return false; // Allow default handling in ProseMirror
},
},
},
}),
Expand Down Expand Up @@ -178,6 +201,17 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
description: 'Something went wrong',
});
}
} else {
this.options.onFileUploadFailed?.(
file.type,
file.size,
'drag-and-drop',
FileUploadFailureType.UPLOADING_NOT_SUPPORTED,
);
this.removeNode(view, placeholderAttrs.id);
notification.error({
message: 'Uploading files in this context is not currently supported',
});
}
} catch (error) {
console.error(error);
Expand All @@ -198,7 +232,7 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
public updateNodeWithUrl(view: EditorView, nodeId: string, url: string): void {
const { nodePos, nodeToUpdate } = this.findNodeById(view.state, nodeId);

if (!nodePos || !nodeToUpdate) return;
if (nodePos === null || !nodeToUpdate) return;

const { name, type } = nodeToUpdate.attrs;

Expand All @@ -211,7 +245,7 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {

public removeNode(view: EditorView, nodeId: string) {
const { nodePos, nodeToUpdate } = this.findNodeById(view.state, nodeId);
if (!nodePos || !nodeToUpdate) return;
if (nodePos === null || !nodeToUpdate) return;

const updatedTransaction = view.state.tr.delete(nodePos, nodePos + nodeToUpdate.nodeSize);
view.dispatch(updatedTransaction);
Expand Down Expand Up @@ -298,12 +332,11 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {

createNodeSpec(extra: ApplySchemaAttributes, override: Partial<NodeSpecOverride>): NodeExtensionSpec {
return {
inline: false,
group: 'block',
marks: '',
selectable: true,
draggable: true,
inline: true,
group: 'inline',
atom: true,
selectable: true,
draggable: (state) => state.editable,
...override,
attrs: {
...extra.defaults(),
Expand All @@ -315,7 +348,7 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
},
parseDOM: [
{
tag: `div[${FILE_ATTRS.name}]`,
tag: `span[${FILE_ATTRS.name}]`,
getAttrs: (node: string | Node) => this.parseFileNode(node, extra),
},
{
Expand All @@ -335,9 +368,10 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
[FILE_ATTRS.type]: type,
[FILE_ATTRS.size]: size.toString(),
[FILE_ATTRS.id]: id,
contenteditable: 'false',
};

return ['div', attrs, name];
return ['span', attrs, name];
},
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,49 @@
import { NodeViewComponentProps } from '@remirror/react';
import { Typography } from 'antd';
import React from 'react';
import styled from 'styled-components';

import {
FILE_ATTRS,
FileNodeAttributes,
getExtensionFromFileName,
getFileIconFromExtension,
handleFileDownload,
} from '@components/components/Editor/extensions/fileDragDrop/fileUtils';
import { Icon } from '@components/components/Icon';
import { colors } from '@components/theme';

import Loading from '@app/shared/Loading';

const FileContainer = styled.div`
max-width: 100%;
const FileContainer = styled.span`
width: fit-content;
display: inline-block;
padding: 4px;
cursor: pointer;
color: ${({ theme }) => theme.styles['primary-color']};

:hover {
border-radius: 8px;
background-color: ${colors.gray[1500]};
}

.ProseMirror-selectednode > & {
border-radius: 8px;
background-color: ${colors.gray[1500]};
}
`;

const FileDetails = styled.div`
min-width: 0; // Allows text truncation
cursor: pointer;
const FileDetails = styled.span`
width: fit-content;
display: flex;
gap: 4px;
align-items: center;
color: ${({ theme }) => theme.styles['primary-color']};
font-weight: 600;
max-width: 350px;
`;

const FileName = styled.span`
const FileName = styled(Typography.Text)`
color: ${({ theme }) => theme.styles['primary-color']};
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
Expand Down Expand Up @@ -66,6 +82,9 @@ export const FileNodeView: React.FC<FileNodeViewProps> = ({ node, onFileDownload
);
}

const extension = getExtensionFromFileName(name);
const icon = getFileIconFromExtension(extension || '');

return (
<FileContainer {...containerProps}>
<FileDetails
Expand All @@ -76,8 +95,8 @@ export const FileNodeView: React.FC<FileNodeViewProps> = ({ node, onFileDownload
handleFileDownload(url, name);
}}
>
<Icon icon="FileArrowDown" size="lg" source="phosphor" />
<FileName>{name}</FileName>
<Icon icon={icon} size="lg" source="phosphor" />
<FileName ellipsis={{ tooltip: name }}>{name}</FileName>
</FileDetails>
</FileContainer>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createFileNodeAttributes,
generateFileId,
getExtensionFromFileName,
getFileIconFromExtension,
getFileTypeFromFilename,
getFileTypeFromUrl,
handleFileDownload,
Expand Down Expand Up @@ -294,4 +295,68 @@ describe('fileUtils', () => {
expect(getExtensionFromFileName('file.TXT')).toBe('txt');
});
});

describe('getFileIconFromExtension', () => {
it('should return FilePdf for pdf extension', () => {
expect(getFileIconFromExtension('pdf')).toBe('FilePdf');
expect(getFileIconFromExtension('PDF')).toBe('FilePdf'); // case-insensitive
});

it('should return FileWord for doc and docx extensions', () => {
expect(getFileIconFromExtension('doc')).toBe('FileWord');
expect(getFileIconFromExtension('DOCX')).toBe('FileWord');
});

it('should return FileText for txt, md, rtf extensions', () => {
expect(getFileIconFromExtension('txt')).toBe('FileText');
expect(getFileIconFromExtension('md')).toBe('FileText');
expect(getFileIconFromExtension('RTF')).toBe('FileText');
});

it('should return FileXls for xls and xlsx extensions', () => {
expect(getFileIconFromExtension('xls')).toBe('FileXls');
expect(getFileIconFromExtension('XLSX')).toBe('FileXls');
});

it('should return FilePpt for ppt and pptx extensions', () => {
expect(getFileIconFromExtension('ppt')).toBe('FilePpt');
expect(getFileIconFromExtension('PPTX')).toBe('FilePpt');
});

it('should return FileImage for image extensions', () => {
['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'tiff'].forEach((ext) => {
expect(getFileIconFromExtension(ext)).toBe('FileImage');
expect(getFileIconFromExtension(ext.toUpperCase())).toBe('FileImage');
});
});

it('should return FileVideo for video extensions', () => {
['mp4', 'wmv', 'mov'].forEach((ext) => {
expect(getFileIconFromExtension(ext)).toBe('FileVideo');
expect(getFileIconFromExtension(ext.toUpperCase())).toBe('FileVideo');
});
});

it('should return FileAudio for mp3 extension', () => {
expect(getFileIconFromExtension('mp3')).toBe('FileAudio');
expect(getFileIconFromExtension('MP3')).toBe('FileAudio');
});

it('should return FileZip for archive extensions', () => {
['zip', 'rar', 'gz'].forEach((ext) => {
expect(getFileIconFromExtension(ext)).toBe('FileZip');
expect(getFileIconFromExtension(ext.toUpperCase())).toBe('FileZip');
});
});

it('should return FileCsv for csv extension', () => {
expect(getFileIconFromExtension('csv')).toBe('FileCsv');
expect(getFileIconFromExtension('CSV')).toBe('FileCsv');
});

it('should return FileArrowDown for unknown extensions', () => {
expect(getFileIconFromExtension('unknown')).toBe('FileArrowDown');
expect(getFileIconFromExtension('')).toBe('FileArrowDown');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,50 @@ export const getFileTypeFromUrl = (url: string): string => {
export const getFileTypeFromFilename = (filename: string): string => {
return getFileTypeFromUrl(filename);
};

/**
* Get icon to show based on file extension
* @param extension - the extension of the file
* @returns string depicting the phosphor icon name
*/
export const getFileIconFromExtension = (extension: string) => {
switch (extension.toLowerCase()) {
case 'pdf':
return 'FilePdf';
case 'doc':
case 'docx':
return 'FileWord';
case 'txt':
case 'md':
case 'rtf':
return 'FileText';
case 'xls':
case 'xlsx':
return 'FileXls';
case 'ppt':
case 'pptx':
return 'FilePpt';
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
case 'webp':
case 'bmp':
case 'tiff':
return 'FileImage';
case 'mp4':
case 'wmv':
case 'mov':
return 'FileVideo';
case 'mp3':
return 'FileAudio';
case 'zip':
case 'rar':
case 'gz':
return 'FileZip';
case 'csv':
return 'FileCsv';
default:
return 'FileArrowDown';
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ marked.use({

/* Check if this is a file link (URL points to our file storage) */
if (href && isFileUrl(href)) {
return `<div class="file-node" ${FILE_ATTRS.url}="${href}" ${FILE_ATTRS.name}="${text}"></div>`;
return `<span class="file-node" ${FILE_ATTRS.url}="${href}" ${FILE_ATTRS.name}="${text}"></span>`;
}

/* Returning false allows marked to use the default link parser */
Expand Down
Loading
Loading