mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-07-21 00:12:02 +00:00
feat(ui): add html to markdown conversion and vice versa in block editor (#13122)
* feat(ui): add html to markdown conversion and vice versa in block editor * chore: update mention suggestion logic * chore: add field support in entityLink * chore: set focus after setting the content * revert: chore: set focus after setting the content * chore: change the prop name * chore: add options to setContent * chore: move parsing option to constant * chore: add diff view custom node * chore: add custom extension for image * chore: address comment * chore: address comment
This commit is contained in:
parent
621afae8d4
commit
e08a3dc7ad
@ -15,29 +15,53 @@ import { EDITOR_OPTIONS } from 'constants/BlockEditor.constants';
|
|||||||
import { isEmpty, isNil } from 'lodash';
|
import { isEmpty, isNil } from 'lodash';
|
||||||
import React, { FC, useEffect, useState } from 'react';
|
import React, { FC, useEffect, useState } from 'react';
|
||||||
import tippy, { Instance, Props } from 'tippy.js';
|
import tippy, { Instance, Props } from 'tippy.js';
|
||||||
|
import {
|
||||||
|
getBackendFormat,
|
||||||
|
getFrontEndFormat,
|
||||||
|
HTMLToMarkdown,
|
||||||
|
MarkdownToHTMLConverter,
|
||||||
|
} from 'utils/FeedUtils';
|
||||||
import './block-editor.less';
|
import './block-editor.less';
|
||||||
import BubbleMenu from './BubbleMenu/BubbleMenu';
|
import BubbleMenu from './BubbleMenu/BubbleMenu';
|
||||||
|
import ImageModal, { ImageData } from './ImageModal/ImageModal';
|
||||||
import LinkModal, { LinkData } from './LinkModal/LinkModal';
|
import LinkModal, { LinkData } from './LinkModal/LinkModal';
|
||||||
import LinkPopup from './LinkPopup/LinkPopup';
|
import LinkPopup from './LinkPopup/LinkPopup';
|
||||||
|
|
||||||
export interface BlockEditorProps {
|
export interface BlockEditorProps {
|
||||||
|
// should be markdown string
|
||||||
content?: string;
|
content?: string;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
|
// will be call with markdown content
|
||||||
|
onChange?: (content: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BlockEditor: FC<BlockEditorProps> = ({
|
const BlockEditor: FC<BlockEditorProps> = ({
|
||||||
content = '',
|
content = '',
|
||||||
editable = true,
|
editable = true,
|
||||||
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [isLinkModalOpen, setIsLinkModalOpen] = useState<boolean>(false);
|
const [isLinkModalOpen, setIsLinkModalOpen] = useState<boolean>(false);
|
||||||
|
const [isImageModalOpen, setIsImageModalOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
...EDITOR_OPTIONS,
|
...EDITOR_OPTIONS,
|
||||||
|
onUpdate({ editor }) {
|
||||||
|
const htmlContent = editor.getHTML();
|
||||||
|
|
||||||
|
const markdown = HTMLToMarkdown.turndown(htmlContent);
|
||||||
|
|
||||||
|
const backendFormat = getBackendFormat(markdown);
|
||||||
|
|
||||||
|
onChange?.(backendFormat);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleLinkToggle = () => {
|
const handleLinkToggle = () => {
|
||||||
setIsLinkModalOpen((prev) => !prev);
|
setIsLinkModalOpen((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
const handleImageToggle = () => {
|
||||||
|
setIsImageModalOpen((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
const handleLinkCancel = () => {
|
const handleLinkCancel = () => {
|
||||||
handleLinkToggle();
|
handleLinkToggle();
|
||||||
@ -132,6 +156,16 @@ const BlockEditor: FC<BlockEditorProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddImage = (values: ImageData) => {
|
||||||
|
if (isNil(editor)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.chain().focus().setImage({ src: values.src }).run();
|
||||||
|
|
||||||
|
handleImageToggle();
|
||||||
|
};
|
||||||
|
|
||||||
const menus = !isNil(editor) && (
|
const menus = !isNil(editor) && (
|
||||||
<BubbleMenu editor={editor} toggleLink={handleLinkToggle} />
|
<BubbleMenu editor={editor} toggleLink={handleLinkToggle} />
|
||||||
);
|
);
|
||||||
@ -145,7 +179,10 @@ const BlockEditor: FC<BlockEditorProps> = ({
|
|||||||
// mentioned here https://github.com/ueberdosis/tiptap/issues/3764#issuecomment-1546854730
|
// mentioned here https://github.com/ueberdosis/tiptap/issues/3764#issuecomment-1546854730
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (content !== undefined) {
|
if (content !== undefined) {
|
||||||
editor.commands.setContent(content);
|
const htmlContent = MarkdownToHTMLConverter.makeHtml(
|
||||||
|
getFrontEndFormat(content)
|
||||||
|
);
|
||||||
|
editor.commands.setContent(htmlContent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [content, editor]);
|
}, [content, editor]);
|
||||||
@ -175,6 +212,13 @@ const BlockEditor: FC<BlockEditorProps> = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isImageModalOpen && (
|
||||||
|
<ImageModal
|
||||||
|
isOpen={isImageModalOpen}
|
||||||
|
onCancel={handleImageToggle}
|
||||||
|
onSave={handleAddImage}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="block-editor-wrapper">
|
<div className="block-editor-wrapper">
|
||||||
<EditorContent editor={editor} onMouseDown={handleLinkPopup} />
|
<EditorContent editor={editor} onMouseDown={handleLinkPopup} />
|
||||||
{menus}
|
{menus}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Collate.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { mergeAttributes, Node } from '@tiptap/core';
|
||||||
|
|
||||||
|
export default Node.create({
|
||||||
|
name: 'diffView',
|
||||||
|
content: 'inline*',
|
||||||
|
group: 'inline',
|
||||||
|
inline: true,
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
class: {
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
tag: 'diff-view',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return ['diff-view', mergeAttributes(HTMLAttributes), 0];
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Collate.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { mergeAttributes, Node, nodeInputRule } from '@tiptap/core';
|
||||||
|
import { IMAGE_INPUT_REGEX } from 'constants/BlockEditor.constants';
|
||||||
|
|
||||||
|
export interface ImageOptions {
|
||||||
|
inline: boolean;
|
||||||
|
allowBase64: boolean;
|
||||||
|
HTMLAttributes: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@tiptap/core' {
|
||||||
|
interface Commands<ReturnType> {
|
||||||
|
image: {
|
||||||
|
/**
|
||||||
|
* Add an image
|
||||||
|
*/
|
||||||
|
setImage: (options: {
|
||||||
|
src: string;
|
||||||
|
alt?: string;
|
||||||
|
title?: string;
|
||||||
|
}) => ReturnType;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Image = Node.create<ImageOptions>({
|
||||||
|
name: 'image',
|
||||||
|
selectable: false,
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
inline: false,
|
||||||
|
allowBase64: false,
|
||||||
|
HTMLAttributes: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
inline() {
|
||||||
|
return this.options.inline;
|
||||||
|
},
|
||||||
|
|
||||||
|
group() {
|
||||||
|
return this.options.inline ? 'inline' : 'block';
|
||||||
|
},
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
src: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
alt: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
tag: this.options.allowBase64
|
||||||
|
? 'img[src]'
|
||||||
|
: 'img[src]:not([src^="data:"])',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return [
|
||||||
|
'img',
|
||||||
|
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
addCommands() {
|
||||||
|
return {
|
||||||
|
setImage:
|
||||||
|
(options) =>
|
||||||
|
({ commands }) => {
|
||||||
|
return commands.insertContent({
|
||||||
|
type: this.name,
|
||||||
|
attrs: options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
addInputRules() {
|
||||||
|
return [
|
||||||
|
nodeInputRule({
|
||||||
|
find: IMAGE_INPUT_REGEX,
|
||||||
|
type: this.type,
|
||||||
|
getAttributes: (match) => {
|
||||||
|
const [, , alt, src, title] = match;
|
||||||
|
|
||||||
|
return { src, alt, title };
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
@ -15,8 +15,9 @@ import tippy, { Instance, Props } from 'tippy.js';
|
|||||||
|
|
||||||
import { SuggestionKeyDownProps, SuggestionProps } from '@tiptap/suggestion';
|
import { SuggestionKeyDownProps, SuggestionProps } from '@tiptap/suggestion';
|
||||||
import { WILD_CARD_CHAR } from 'constants/char.constants';
|
import { WILD_CARD_CHAR } from 'constants/char.constants';
|
||||||
import { getTeamAndUserDetailsPath, getUserPath } from 'constants/constants';
|
import { EntityUrlMapType, ENTITY_URL_MAP } from 'constants/Feeds.constants';
|
||||||
import { getSearchedUsers, getUserSuggestions } from 'rest/miscAPI';
|
import { getSearchedUsers, getUserSuggestions } from 'rest/miscAPI';
|
||||||
|
import { buildMentionLink } from 'utils/FeedUtils';
|
||||||
import { ExtensionRef } from '../BlockEditor.interface';
|
import { ExtensionRef } from '../BlockEditor.interface';
|
||||||
import MentionList from './MentionList';
|
import MentionList from './MentionList';
|
||||||
|
|
||||||
@ -31,10 +32,10 @@ export const mentionSuggestion = () => ({
|
|||||||
name: hit._source.name,
|
name: hit._source.name,
|
||||||
label: hit._source.displayName,
|
label: hit._source.displayName,
|
||||||
fqn: hit._source.fullyQualifiedName,
|
fqn: hit._source.fullyQualifiedName,
|
||||||
href:
|
href: buildMentionLink(
|
||||||
hit._source.entityType === 'user'
|
ENTITY_URL_MAP[hit._source.entityType as EntityUrlMapType],
|
||||||
? getUserPath(hit._source.fullyQualifiedName ?? '')
|
hit._source.name
|
||||||
: getTeamAndUserDetailsPath(hit._source.fullyQualifiedName ?? ''),
|
),
|
||||||
type: hit._source.entityType,
|
type: hit._source.entityType,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
@ -46,10 +47,10 @@ export const mentionSuggestion = () => ({
|
|||||||
name: hit._source.name,
|
name: hit._source.name,
|
||||||
label: hit._source.displayName,
|
label: hit._source.displayName,
|
||||||
fqn: hit._source.fullyQualifiedName,
|
fqn: hit._source.fullyQualifiedName,
|
||||||
href:
|
href: buildMentionLink(
|
||||||
hit._source.entityType === 'user'
|
ENTITY_URL_MAP[hit._source.entityType as EntityUrlMapType],
|
||||||
? getUserPath(hit._source.fullyQualifiedName ?? '')
|
hit._source.name
|
||||||
: getTeamAndUserDetailsPath(hit._source.fullyQualifiedName ?? ''),
|
),
|
||||||
type: hit._source.entityType,
|
type: hit._source.entityType,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Collate.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { Form, FormProps, Input, Modal } from 'antd';
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
|
export interface ImageData {
|
||||||
|
src: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LinkModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onSave: (data: ImageData) => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageModal: FC<LinkModalProps> = ({ isOpen, onSave, onCancel }) => {
|
||||||
|
const handleSubmit: FormProps<ImageData>['onFinish'] = (values) => {
|
||||||
|
onSave(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="block-editor-image-modal"
|
||||||
|
maskClosable={false}
|
||||||
|
okButtonProps={{
|
||||||
|
htmlType: 'submit',
|
||||||
|
id: 'image-form',
|
||||||
|
form: 'image-form',
|
||||||
|
}}
|
||||||
|
okText="Save"
|
||||||
|
open={isOpen}
|
||||||
|
onCancel={onCancel}>
|
||||||
|
<Form
|
||||||
|
data-testid="image-form"
|
||||||
|
id="image-form"
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={handleSubmit}>
|
||||||
|
<Form.Item label="Image link" name="src">
|
||||||
|
<Input autoFocus required />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageModal;
|
@ -32,6 +32,7 @@ const LinkModal: FC<LinkModalProps> = ({ isOpen, data, onSave, onCancel }) => {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className="block-editor-link-modal"
|
className="block-editor-link-modal"
|
||||||
|
maskClosable={false}
|
||||||
okButtonProps={{
|
okButtonProps={{
|
||||||
htmlType: 'submit',
|
htmlType: 'submit',
|
||||||
id: 'link-form',
|
id: 'link-form',
|
||||||
|
@ -232,7 +232,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-editor-link-modal {
|
.block-editor-link-modal,
|
||||||
|
.block-editor-image-modal {
|
||||||
.ant-modal-content {
|
.ant-modal-content {
|
||||||
.ant-modal-body {
|
.ant-modal-body {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
@ -16,8 +16,10 @@ import Placeholder from '@tiptap/extension-placeholder';
|
|||||||
import TaskItem from '@tiptap/extension-task-item';
|
import TaskItem from '@tiptap/extension-task-item';
|
||||||
import TaskList from '@tiptap/extension-task-list';
|
import TaskList from '@tiptap/extension-task-list';
|
||||||
import StarterKit from '@tiptap/starter-kit';
|
import StarterKit from '@tiptap/starter-kit';
|
||||||
|
import DiffView from 'components/BlockEditor/Extensions/diffView';
|
||||||
import { Hashtag } from 'components/BlockEditor/Extensions/hashtag';
|
import { Hashtag } from 'components/BlockEditor/Extensions/hashtag';
|
||||||
import { hashtagSuggestion } from 'components/BlockEditor/Extensions/hashtag/hashtagSuggestion';
|
import { hashtagSuggestion } from 'components/BlockEditor/Extensions/hashtag/hashtagSuggestion';
|
||||||
|
import { Image } from 'components/BlockEditor/Extensions/image';
|
||||||
import { Mention } from 'components/BlockEditor/Extensions/mention';
|
import { Mention } from 'components/BlockEditor/Extensions/mention';
|
||||||
import { mentionSuggestion } from 'components/BlockEditor/Extensions/mention/mentionSuggestions';
|
import { mentionSuggestion } from 'components/BlockEditor/Extensions/mention/mentionSuggestions';
|
||||||
import slashCommand from 'components/BlockEditor/Extensions/slashCommand';
|
import slashCommand from 'components/BlockEditor/Extensions/slashCommand';
|
||||||
@ -98,6 +100,11 @@ export const EDITOR_OPTIONS: Partial<EditorOptions> = {
|
|||||||
Hashtag.configure({
|
Hashtag.configure({
|
||||||
suggestion: hashtagSuggestion(),
|
suggestion: hashtagSuggestion(),
|
||||||
}),
|
}),
|
||||||
|
DiffView,
|
||||||
|
Image.configure({
|
||||||
|
allowBase64: true,
|
||||||
|
inline: true,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
|
|
||||||
enableInputRules: [
|
enableInputRules: [
|
||||||
@ -112,4 +119,10 @@ export const EDITOR_OPTIONS: Partial<EditorOptions> = {
|
|||||||
'orderedList',
|
'orderedList',
|
||||||
'strike',
|
'strike',
|
||||||
],
|
],
|
||||||
|
parseOptions: {
|
||||||
|
preserveWhitespace: 'full',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const IMAGE_INPUT_REGEX =
|
||||||
|
/(?:^|\s)(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/;
|
||||||
|
@ -22,11 +22,13 @@ export const teamsLinkRegEx = /\((.+?\/\/.+?)\/(.+?\/.+?\/.+?)\/(.+?)\)/;
|
|||||||
export const entityLinkRegEx = /<#E::([^<>]+?)::([^<>]+?)>/g;
|
export const entityLinkRegEx = /<#E::([^<>]+?)::([^<>]+?)>/g;
|
||||||
export const entityRegex = /<#E::([^<>]+?)::([^<>]+?)\|(\[(.+?)?\]\((.+?)?\))>/;
|
export const entityRegex = /<#E::([^<>]+?)::([^<>]+?)\|(\[(.+?)?\]\((.+?)?\))>/;
|
||||||
|
|
||||||
export const entityUrlMap = {
|
export const ENTITY_URL_MAP = {
|
||||||
team: 'settings/members/teams',
|
team: 'settings/members/teams',
|
||||||
user: 'users',
|
user: 'users',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EntityUrlMapType = keyof typeof ENTITY_URL_MAP;
|
||||||
|
|
||||||
export const confirmStateInitialValue = {
|
export const confirmStateInitialValue = {
|
||||||
state: false,
|
state: false,
|
||||||
threadId: undefined,
|
threadId: undefined,
|
||||||
|
@ -109,7 +109,11 @@ export default class EntityLink {
|
|||||||
* @param string entityFqn
|
* @param string entityFqn
|
||||||
* @returns entityLink
|
* @returns entityLink
|
||||||
*/
|
*/
|
||||||
static getEntityLink(entityType: string, entityFqn: string) {
|
static getEntityLink(entityType: string, entityFqn: string, field?: string) {
|
||||||
|
if (field) {
|
||||||
|
return `<#E${ENTITY_LINK_SEPARATOR}${entityType}${ENTITY_LINK_SEPARATOR}${entityFqn}${ENTITY_LINK_SEPARATOR}${field}>`;
|
||||||
|
}
|
||||||
|
|
||||||
return `<#E${ENTITY_LINK_SEPARATOR}${entityType}${ENTITY_LINK_SEPARATOR}${entityFqn}>`;
|
return `<#E${ENTITY_LINK_SEPARATOR}${entityType}${ENTITY_LINK_SEPARATOR}${entityFqn}>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,9 @@
|
|||||||
import { Tooltip } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
import { DE_ACTIVE_COLOR } from 'constants/constants';
|
import { DE_ACTIVE_COLOR } from 'constants/constants';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { isUndefined } from 'lodash';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ReactComponent as IconComments } from '../assets/svg/comment.svg';
|
import { ReactComponent as IconComments } from '../assets/svg/comment.svg';
|
||||||
import { entityUrlMap } from '../constants/Feeds.constants';
|
|
||||||
import { ThreadType } from '../generated/entity/feed/thread';
|
import { ThreadType } from '../generated/entity/feed/thread';
|
||||||
import { EntityReference } from '../generated/entity/teams/user';
|
|
||||||
import { getEntityFeedLink } from './EntityUtils';
|
import { getEntityFeedLink } from './EntityUtils';
|
||||||
|
|
||||||
const iconsProps = {
|
const iconsProps = {
|
||||||
@ -58,19 +55,3 @@ export const getFieldThreadElement = (
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultValue = (owner: EntityReference) => {
|
|
||||||
const message = t('message.can-you-add-a-description');
|
|
||||||
if (isUndefined(owner)) {
|
|
||||||
return `${message}`;
|
|
||||||
} else {
|
|
||||||
const name = owner.name;
|
|
||||||
const displayName = owner.displayName;
|
|
||||||
const entityType = owner.type;
|
|
||||||
const mention = `<a href=${`/${
|
|
||||||
entityUrlMap[entityType as keyof typeof entityUrlMap]
|
|
||||||
}/${name}`}>@${displayName}</a>`;
|
|
||||||
|
|
||||||
return `${mention} ${message}`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -42,7 +42,8 @@ import {
|
|||||||
entityLinkRegEx,
|
entityLinkRegEx,
|
||||||
EntityRegEx,
|
EntityRegEx,
|
||||||
entityRegex,
|
entityRegex,
|
||||||
entityUrlMap,
|
EntityUrlMapType,
|
||||||
|
ENTITY_URL_MAP,
|
||||||
hashtagRegEx,
|
hashtagRegEx,
|
||||||
linkRegEx,
|
linkRegEx,
|
||||||
mentionRegEx,
|
mentionRegEx,
|
||||||
@ -219,7 +220,7 @@ export async function suggestions(
|
|||||||
id: hit._id,
|
id: hit._id,
|
||||||
value: name,
|
value: name,
|
||||||
link: buildMentionLink(
|
link: buildMentionLink(
|
||||||
entityUrlMap[entityType as keyof typeof entityUrlMap],
|
ENTITY_URL_MAP[entityType as EntityUrlMapType],
|
||||||
hit._source.name
|
hit._source.name
|
||||||
),
|
),
|
||||||
name: hit._source.name,
|
name: hit._source.name,
|
||||||
@ -246,7 +247,7 @@ export async function suggestions(
|
|||||||
id: hit._id,
|
id: hit._id,
|
||||||
value: name,
|
value: name,
|
||||||
link: buildMentionLink(
|
link: buildMentionLink(
|
||||||
entityUrlMap[entityType as keyof typeof entityUrlMap],
|
ENTITY_URL_MAP[entityType as EntityUrlMapType],
|
||||||
hit._source.name
|
hit._source.name
|
||||||
),
|
),
|
||||||
name: hit._source.name,
|
name: hit._source.name,
|
||||||
@ -352,7 +353,7 @@ export const getBackendFormat = (message: string) => {
|
|||||||
const hashtagList = [...new Set(getHashTagList(message) ?? [])];
|
const hashtagList = [...new Set(getHashTagList(message) ?? [])];
|
||||||
const mentionDetails = mentionList.map((m) => getEntityDetail(m) ?? []);
|
const mentionDetails = mentionList.map((m) => getEntityDetail(m) ?? []);
|
||||||
const hashtagDetails = hashtagList.map((h) => getEntityDetail(h) ?? []);
|
const hashtagDetails = hashtagList.map((h) => getEntityDetail(h) ?? []);
|
||||||
const urlEntries = Object.entries(entityUrlMap);
|
const urlEntries = Object.entries(ENTITY_URL_MAP);
|
||||||
|
|
||||||
mentionList.forEach((m, i) => {
|
mentionList.forEach((m, i) => {
|
||||||
const updatedDetails = mentionDetails[i].slice(-2);
|
const updatedDetails = mentionDetails[i].slice(-2);
|
||||||
@ -594,6 +595,9 @@ export const entityDisplayName = (entityType: string, entityFQN: string) => {
|
|||||||
|
|
||||||
export const MarkdownToHTMLConverter = new Showdown.Converter({
|
export const MarkdownToHTMLConverter = new Showdown.Converter({
|
||||||
strikethrough: true,
|
strikethrough: true,
|
||||||
|
tables: true,
|
||||||
|
tasklists: true,
|
||||||
|
simpleLineBreaks: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getFeedPanelHeaderText = (
|
export const getFeedPanelHeaderText = (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user