Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.linkedin.datahub.graphql.generated.GetPresignedUploadUrlResponse;
import com.linkedin.datahub.graphql.generated.UploadDownloadScenario;
import com.linkedin.datahub.graphql.resolvers.mutate.DescriptionUtils;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.config.S3Configuration;
import com.linkedin.metadata.utils.aws.S3Util;
import graphql.schema.DataFetcher;
Expand Down Expand Up @@ -107,7 +108,9 @@ private void validateInputForAssetDocumentationScenario(
}

private String generateNewFileId(final GetPresignedUploadUrlInput input) {
return String.format("%s-%s", UUID.randomUUID().toString(), input.getFileName());
return String.format(
"%s%s%s",
UUID.randomUUID().toString(), Constants.S3_FILE_ID_NAME_SEPARATOR, input.getFileName());
}

private String getS3Key(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export const SUPPORTED_FILE_TYPES = [
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel',
'application/xml',
'application/vnd.ms-powerpoint',
Expand All @@ -45,6 +44,20 @@ export const SUPPORTED_FILE_TYPES = [
'audio/mpeg',
'video/x-ms-wmv',
'image/tiff',
'text/x-python-script',
'application/json',
'text/html',
'text/x-java-source',
'image/svg+xml',
'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.oasis.opendocument.presentation',
'text/css',
'application/javascript',
'text/x-yaml',
'application/x-tar',
'text/x-sql',
'application/x-sh',
];

const EXTENSION_TO_FILE_TYPE = {
Expand Down Expand Up @@ -73,6 +86,23 @@ const EXTENSION_TO_FILE_TYPE = {
tiff: 'image/tiff',
md: 'text/markdown',
csv: 'text/csv',
py: 'text/x-python-script',
json: 'application/json',
html: 'text/html',
java: 'text/x-java-source',
svg: 'image/svg+xml',
log: 'text/plain',
mov: 'video/quicktime',
odt: 'application/vnd.oasis.opendocument.text',
ods: 'application/vnd.oasis.opendocument.spreadsheet',
odp: 'application/vnd.oasis.opendocument.presentation',
css: 'text/css',
js: 'application/javascript',
yaml: 'text/x-yaml',
yml: 'text/x-yaml',
tar: 'application/x-tar',
sql: 'text/x-sql',
sh: 'application/x-sh',
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,24 @@ const turndownService = new TurndownService({
replacement: (content, node: any) =>
node?.previousElementSibling?.nodeName === 'P' ? `${br}${content}` : content,
})
/* Keep HTML format if image has an explicit width attribute */
/* Handle images with width attributes and wrap URLs in angle brackets for all images */
.addRule('images', {
filter: (node) => node.nodeName === 'IMG' && node.hasAttribute('width'),
replacement: (_, node: any) => `${node.outerHTML}`,
filter: (node) => node.nodeName === 'IMG',
replacement: (test, node: any) => {
// Keep HTML format if image has an explicit width attribute
if (node.hasAttribute('width')) {
return `${node.outerHTML}`;
}

// For standard images, wrap URL in angle brackets to support spaces
const src = node.getAttribute('src') || '';
const alt = node.getAttribute('alt') || '';

// Wrap URL in angle brackets to support spaces and special characters in URLs
const encodedUrl = `<${src}>`;

return `![${alt}](${encodedUrl})`;
},
})
/* Add improved code block support from html (snippet from Remirror). */
.addRule('fencedCodeBlock', {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Image } from '@phosphor-icons/react';
import { useCommands } from '@remirror/react';
import { Form } from 'antd';
import { FormInstance } from 'antd/es/form/Form';
import React, { useState } from 'react';
import styled from 'styled-components';

import { Button } from '@components/components/Button';
import { Dropdown } from '@components/components/Dropdown';
import { CommandButton } from '@components/components/Editor/toolbar/CommandButton';
import { FileUploadContent } from '@components/components/Editor/toolbar/FileUploadContent';
import { Input } from '@components/components/Input';

import ButtonTabs from '@app/homeV3/modules/shared/ButtonTabs/ButtonTabs';
import { colors } from '@src/alchemy-components/theme';

const UPLOAD_FILE_KEY = 'uploadFile';
const URL_KEY = 'url';

const ContentWrapper = styled.div`
width: 300px;
background-color: ${colors.white};
box-shadow: 0 4px 12px 0 rgba(9, 1, 61, 0.12);
display: flex;
flex-direction: column;
padding: 8px;
gap: 8px;
border-radius: 12px;
`;

const StyledButton = styled(Button)`
width: 100%;
display: flex;
justify-content: center;
`;

const FormItem = styled(Form.Item)`
margin-bottom: 8px;
`;

function ImageUrlInput({ form, hideDropdown }: { form: FormInstance<any>; hideDropdown: () => void }) {
const { insertImage } = useCommands();

const handleOk = () => {
form.validateFields()
.then((values) => {
form.resetFields();
hideDropdown();
insertImage(values);
})
.catch((info) => {
console.log('Validate Failed:', info);
});
};

return (
<Form form={form} layout="vertical" colon={false} requiredMark={false}>
<FormItem name="src" rules={[{ required: true }]}>
<Input label="Image URL" placeholder="http://www.example.com/image.jpg" autoFocus />
</FormItem>
<FormItem name="alt">
<Input label="Alt Text" />
</FormItem>
<StyledButton onClick={handleOk}>Embed Image</StyledButton>
</Form>
);
}

export const AddImageButtonV2 = () => {
const [showDropdown, setShowDropdown] = useState(false);
const [form] = Form.useForm();

const tabs = [
{
key: UPLOAD_FILE_KEY,
label: 'Upload File',
content: <FileUploadContent hideDropdown={() => setShowDropdown(false)} />,
},
{
key: URL_KEY,
label: 'URL',
content: <ImageUrlInput form={form} hideDropdown={() => setShowDropdown(false)} />,
},
];

const handleButtonClick = () => {
setShowDropdown(true);
};

const dropdownContent = () => (
<ContentWrapper>
<ButtonTabs tabs={tabs} defaultKey={UPLOAD_FILE_KEY} />
</ContentWrapper>
);

return (
<>
<Dropdown
open={showDropdown}
onOpenChange={(open) => setShowDropdown(open)}
dropdownRender={dropdownContent}
>
<CommandButton
active={false}
icon={<Image size={20} color={colors.gray[1800]} />}
commandName="insertImage"
onClick={handleButtonClick}
/>
</Dropdown>
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { Button, Dropdown, Text, Tooltip, colors, notification } from '@components';
import { Dropdown, Tooltip, colors } from '@components';
import { useRemirrorContext } from '@remirror/react';
import { FileArrowUp } from 'phosphor-react';
import React, { useRef, useState } from 'react';
import React, { useState } from 'react';
import styled from 'styled-components';

import {
FileDragDropExtension,
SUPPORTED_FILE_TYPES,
createFileNodeAttributes,
validateFile,
} from '@components/components/Editor/extensions/fileDragDrop';
import { FileDragDropExtension } from '@components/components/Editor/extensions/fileDragDrop';
import { CommandButton } from '@components/components/Editor/toolbar/CommandButton';
import { FileUploadFailureType } from '@components/components/Editor/types';
import { FileUploadContent } from '@components/components/Editor/toolbar/FileUploadContent';

const DropdownContainer = styled.div`
box-shadow: 0 4px 12px 0 rgba(9, 1, 61, 0.12);
Expand All @@ -24,122 +19,25 @@ const DropdownContainer = styled.div`
background: ${colors.white};
`;

const StyledText = styled(Text)`
text-align: center;
`;

const StyledButton = styled(Button)`
width: 100%;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
`;

const FileInput = styled.input`
display: none;
`;

export const FileUploadButton = () => {
const { commands } = useRemirrorContext();

const fileInputRef = useRef<HTMLInputElement>(null);
const remirrorContext = useRemirrorContext();
const fileExtension = remirrorContext.getExtension(FileDragDropExtension);

const [showDropdown, setShowDropdown] = useState(false);

const handlebuttonClick = () => {
fileInputRef.current?.click();
};

const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
const input = event.target as HTMLInputElement;
const files = input.files ? Array.from(input.files) : [];
if (files.length === 0) return;

const supportedTypes = SUPPORTED_FILE_TYPES;
const { onFileUpload, onFileUploadAttempt, onFileUploadFailed, onFileUploadSucceeded } = fileExtension.options;

try {
// Process files concurrently
await Promise.all(
files.map(async (file) => {
onFileUploadAttempt?.(file.type, file.size, 'button');

const validation = validateFile(file, { allowedTypes: supportedTypes });
if (!validation.isValid) {
console.error(validation.error);
onFileUploadFailed?.(
file.type,
file.size,
'button',
validation.failureType || FileUploadFailureType.UNKNOWN,
);
notification.error({
message: 'Upload Failed',
description: validation.displayError || validation.error,
});
}

// Create placeholder node
const attrs = createFileNodeAttributes(file);
commands.insertFileNode({ ...attrs, url: '' });

// Upload file if handler exists
if (onFileUpload) {
try {
const finalUrl = await onFileUpload(file);
fileExtension.updateNodeWithUrl(remirrorContext.view, attrs.id, finalUrl);
onFileUploadSucceeded?.(file.type, file.size, 'button');
} catch (uploadError) {
console.error(uploadError);
onFileUploadFailed?.(
file.type,
file.size,
'button',
FileUploadFailureType.UNKNOWN,
`${uploadError}`,
);
fileExtension.removeNode(remirrorContext.view, attrs.id);
notification.error({
message: 'Upload Failed',
description: 'Something went wrong',
});
}
}
}),
);
} catch (error) {
console.error(error);
onFileUploadFailed?.(files[0].type, files[0].size, 'button', FileUploadFailureType.UNKNOWN, `${error}`);
notification.error({
message: 'Upload Failed',
description: 'Something went wrong',
});
} finally {
input.value = '';
setShowDropdown(false);
}
};

const dropdownContent = () => (
<DropdownContainer>
<StyledButton size="sm" onClick={handlebuttonClick}>
Choose File
</StyledButton>
<FileInput ref={fileInputRef} type="file" onChange={handleFileChange} />
<StyledText color="gray" size="sm" lineHeight="normal">
Max size: 2GB
</StyledText>
</DropdownContainer>
);

// Hide the button when uploading of files is disabled
if (!fileExtension.options.onFileUpload) return null;

return (
<Dropdown open={showDropdown} onOpenChange={(open) => setShowDropdown(open)} dropdownRender={dropdownContent}>
<Dropdown
open={showDropdown}
onOpenChange={(open) => setShowDropdown(open)}
dropdownRender={() => (
<DropdownContainer>
<FileUploadContent hideDropdown={() => setShowDropdown(false)} />
</DropdownContainer>
)}
>
<Tooltip title="Upload File">
<CommandButton
icon={<FileArrowUp size={20} color={colors.gray[1800]} />}
Expand Down
Loading
Loading