From bf1dddedeb07936345694b7f48e6f0c43f05371a Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Mon, 1 Jul 2024 12:06:43 +0530 Subject: [PATCH] feat(#14234): add LaTeX Support In Block Editor (#16842) * feat(#14234): add LaTeX Support In Block Editor * refactor: improve assignment regex in evaluateExpression helper * refactor: Remove mathematics extension from Block Editor * refactor: Remove mathematics extension from Block Editor * chore: Update npm dependencies * feat: Add react-latex-next package for LaTeX support in Block Editor * refactor: Update MathEquation extension in Block Editor * feat: Add Math Equation slash command option to Block Editor * fix: isEditing attribute should be updated when updateAttributes is called * chore: enable input rule for link and math equation * fix: link mark being removed when cancel operation is performed * feat: add trailing node extension to improve user experience by having empty node at the end * chore: improve link markdown support and link posting support * chore: Update link and math equation input rules * chore: Update link icon size in LinkPopup component * chore: Refactor link handling in EditorSlots component * chore: Update MathEquationComponent to toggle isEditing class when editing math equation * feat: Add placeholder text for math equation input --- .../src/main/resources/ui/package.json | 3 + .../assets/img/ic-format-math-equation.png | Bin 0 -> 1943 bytes .../components/BlockEditor/EditorSlots.tsx | 11 +- .../Extensions/MathEquation/MathEquation.ts | 95 +++++++++++++++++ .../MathEquation/MathEquationComponent.tsx | 96 ++++++++++++++++++ .../MathEquation/math-equation.less | 57 +++++++++++ .../BlockEditor/Extensions/index.ts | 4 + .../components/BlockEditor/Extensions/link.ts | 66 +++++++++--- .../Extensions/slash-command/items.ts | 22 ++++ .../BlockEditor/Extensions/trailing-node.ts | 79 ++++++++++++++ .../BlockEditor/LinkPopup/LinkPopup.tsx | 2 +- .../components/BlockEditor/block-editor.less | 6 +- .../ui/src/constants/BlockEditor.constants.ts | 8 ++ .../ui/src/locale/languages/de-de.json | 1 + .../ui/src/locale/languages/en-us.json | 1 + .../ui/src/locale/languages/es-es.json | 1 + .../ui/src/locale/languages/fr-fr.json | 1 + .../ui/src/locale/languages/he-he.json | 1 + .../ui/src/locale/languages/ja-jp.json | 1 + .../ui/src/locale/languages/nl-nl.json | 1 + .../ui/src/locale/languages/pt-br.json | 1 + .../ui/src/locale/languages/ru-ru.json | 1 + .../ui/src/locale/languages/zh-cn.json | 1 + .../main/resources/ui/webpack.config.dev.js | 1 + .../main/resources/ui/webpack.config.prod.js | 1 + .../src/main/resources/ui/yarn.lock | 19 ++++ 26 files changed, 459 insertions(+), 21 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/img/ic-format-math-equation.png create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquation.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/math-equation.less create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/trailing-node.ts diff --git a/openmetadata-ui/src/main/resources/ui/package.json b/openmetadata-ui/src/main/resources/ui/package.json index 8c092d98d00..7866e84fc1c 100644 --- a/openmetadata-ui/src/main/resources/ui/package.json +++ b/openmetadata-ui/src/main/resources/ui/package.json @@ -96,6 +96,7 @@ "i18next": "^21.10.0", "i18next-browser-languagedetector": "^6.1.6", "jwt-decode": "^3.1.2", + "katex": "^0.16.10", "less": "^4.1.3", "less-loader": "^11.0.0", "lodash": "^4.17.21", @@ -119,6 +120,7 @@ "react-grid-layout": "^1.4.2", "react-helmet-async": "^1.3.0", "react-i18next": "^11.18.6", + "react-latex-next": "^3.0.0", "react-lazylog": "^4.5.3", "react-oidc": "^1.0.3", "react-papaparse": "^4.1.0", @@ -169,6 +171,7 @@ "@types/diff": "^5.0.2", "@types/dompurify": "^3.0.5", "@types/jest": "^26.0.23", + "@types/katex": "^0.16.7", "@types/lodash": "^4.14.167", "@types/luxon": "^3.0.1", "@types/moment": "^2.13.0", diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/img/ic-format-math-equation.png b/openmetadata-ui/src/main/resources/ui/src/assets/img/ic-format-math-equation.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f38aa0ffe8511ded6729088d2b1ddbc8f8a485 GIT binary patch literal 1943 zcmc(g|3A}<9>?c)d^tslFsoKci-giD`EqYGvYD@Qj4;;D85{HE>iROop&Y9;M#FGc zw#gD#MM{g#hK?_jJ|wmzcV}wHmziOa+pgRHa3A-F_xt^LzTZE*UhiMtZ(5-Ld84h6 ztsoG{2+@!OGkZi=`0{Cp1-xWmsa~!!X@E#VNriext?P&F1u}dwEihk+i=tY_5F3HvcrOz4A`LC zoLi^Oxgg0>3nxb)ix*aAWj7teWIE8mx5T5dyh~B#N1pX|d{WvJ;4(7xCZ3?aAf{*7 zZ@7JSY@eHPQyaPHjM81WFkGc#3YQ(^F2_feThW|??%lSzv-Tj<8Dr@{3ocs?_CjB^ z;I)T*ey_@Oey`*`td!)UifrETbJWfbbw!-B^(Otq`>g>X-?y>PlUOKD#df8VpTpVk zR=wSdDO0*7IyT`xpOjw;>i2;ThtzWj9P}*{iyT~kjBOMSkI;2r$+RxApjWKgH1jVp zg-v~$(aV_J-RH#^B?6eYOdo)Qv+57fXaC^toG*pvdjkd#$rS<{f3yB zyG73W^+v)(a}Nv{uQhe(IU0{Fs2>{Y4h@3{8pSn@Tb6(e;1(J@RFyi^(8fKU5|m<3 zTaQcNacD{pa7(xvt}%@Eh1B%LX-k<*?!Oze8~q|AM-K!vDH0)gdlJ=(Tu=TIL8sHN zD+OZ+U=A^g_B+Xj?YTnAc>}i=`y<35d`!a2m{~7yvI)@ILBUlwR4uLB8uzUqZ*o3>k{ zEB;Xq_Jl1ELy$%y&(5g27mMFivP4#O2 z0#{{DJYmXwO#R!v>?ElEt$f+BQ~CX(zpjnw#-j-7MQiKwPl@;1iDYNe4c<2P3-bZH zrhT&F&z`uAE)9_!MyflH47+dgwgeXS^DpuS=-pwok)7Xi%lmi9VuG}8A_16iNEMS;o&*jy{0dR5}$4Oux)f|(Qdo9e$uVd(tRZdDN&kmK&O54C; z?tm@Z386DxOEkH&=!r%sd(vn6f zyww{q*yBS0XoMjYWX6(YsJp!CbO%@R?PRg=JQZxsTI}Kj@i<5#=KcIz>YDi(`ewJY zHNF_zty!9^v9}C`B$@oZ7|R?_4QCL~3UtxF9~pBwd8wU@IP1c47&UyD@szu)$WK)U z?y&VEg?HBw59N03r)Fp)!6-=f>9pmVKnVg*1zBZG@hDRdC`x{xQV3VO=fPLa@5il7 zN~(LQdL^(NSOYO-eN?CfEJT`BGnXdt>H>)}e4|#Khr7nz31@f@fAjfW@&5zFxjwBP VZaNC>*yh6pA-((&&7Lv&{{dc?v_k*@ literal 0 HcmV?d00001 diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/EditorSlots.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/EditorSlots.tsx index 176a02bcfd2..7ecc0f78c1d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/EditorSlots.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/EditorSlots.tsx @@ -35,10 +35,6 @@ const EditorSlots = forwardRef( const handleLinkCancel = () => { handleLinkToggle(); - if (!isNil(editor)) { - editor.chain().focus().extendMarkRange('link').unsetLink().run(); - editor.chain().blur().run(); - } }; const handleLinkSave = (values: LinkData, op: 'edit' | 'add') => { @@ -98,8 +94,11 @@ const EditorSlots = forwardRef( return; } - if (target.nodeName === 'A') { - const href = target.getAttribute('href'); + + const closestElement = target.closest('a'); + if (target.nodeName === 'A' || closestElement) { + const href = + target.getAttribute('href') || closestElement?.getAttribute('href'); component = new ReactRenderer(LinkPopup, { editor: editor as Editor, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquation.ts b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquation.ts new file mode 100644 index 00000000000..d0cbcbdebb3 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquation.ts @@ -0,0 +1,95 @@ +/* + * Copyright 2024 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 { InputRule, mergeAttributes, Node, nodePasteRule } from '@tiptap/core'; +import { ReactNodeViewRenderer } from '@tiptap/react'; +import { MathEquationComponent } from './MathEquationComponent'; + +export default Node.create({ + name: 'MathEquation', + group: 'block', + + atom: true, + + addAttributes() { + return { + math_equation: { + default: '', + }, + isEditing: { + default: false, + }, + }; + }, + + parseHTML() { + return [ + { + tag: 'block-math-equation', + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return ['block-math-equation', mergeAttributes(HTMLAttributes)]; + }, + + addNodeView() { + return ReactNodeViewRenderer(MathEquationComponent); + }, + + addInputRules() { + return [ + new InputRule({ + find: new RegExp(`\\$\\$((?:\\.|[^\\$]|\\$)+?)\\$\\$`, 'g'), + handler: (props) => { + const latex = props.match[0]; + + props + .chain() + .focus() + .deleteRange(props.range) + .insertContent({ + type: 'MathEquation', + attrs: { + math_equation: latex, + }, + }) + .run(); + }, + }), + ]; + }, + + addPasteRules() { + return [ + nodePasteRule({ + find: new RegExp(`\\$((?:\\.|[^\\$]|\\$)+?)\\$$`, 'g'), + type: this.type, + getAttributes: (match) => { + return { + math_equation: match[0], + }; + }, + }), + nodePasteRule({ + find: new RegExp(`\\$\\$((?:\\.|[^\\$]|\\$)+?)\\$\\$`, 'g'), + type: this.type, + getAttributes: (match) => { + return { + math_equation: match[0], + }; + }, + }), + ]; + }, +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx new file mode 100644 index 00000000000..b8aac3b5ad7 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx @@ -0,0 +1,96 @@ +/* + * Copyright 2024 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 { CheckOutlined, CloseOutlined } from '@ant-design/icons'; +import { NodeViewProps, NodeViewWrapper } from '@tiptap/react'; +import { Button, Input, Space, Tooltip } from 'antd'; +import { TextAreaRef } from 'antd/lib/input/TextArea'; +import classNames from 'classnames'; +import 'katex/dist/katex.min.css'; +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import Latex from 'react-latex-next'; +import { ReactComponent as EditIcon } from '../../../../assets/svg/edit-new.svg'; +import './math-equation.less'; + +export const MathEquationComponent: FC = ({ + node, + updateAttributes, +}) => { + const { t } = useTranslation(); + const inputRef = React.useRef(null); + const equation = node.attrs.math_equation; + + const [isEditing, setIsEditing] = React.useState( + Boolean(node.attrs.isEditing) + ); + + const handleSaveEquation = () => { + updateAttributes({ + math_equation: + inputRef.current?.resizableTextArea?.textArea.value ?? equation, + isEditing: false, + }); + setIsEditing(false); + }; + + return ( + +
+ {isEditing ? ( +
+ + +
+ ) : ( + {equation} + )} + {!isEditing && ( + +
+
+ ); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/math-equation.less b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/math-equation.less new file mode 100644 index 00000000000..e5c089528e2 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/math-equation.less @@ -0,0 +1,57 @@ +/* + * Copyright 2024 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. + */ +.math-equation-wrapper { + display: flex; + gap: 8px; + flex-wrap: wrap; + background: transparent; + justify-content: space-between; + align-items: center; + padding: 8px; + border-radius: 4px; + .edit-button { + opacity: 0; + padding: 4px; + } + &:hover { + .edit-button { + opacity: 1; + } + } + + .math-equation-edit-input-wrapper { + width: 100%; + display: flex; + justify-content: space-between; + } + + &.isediting { + background-color: black; + .edit-button, + .math-equation-input { + color: white; + } + } +} + +.react-renderer.node-MathEquation.ProseMirror-selectednode { + background-color: black; + .math-equation-wrapper { + .edit-button { + color: white; + } + } + &.has-focus { + color: white; + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/index.ts b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/index.ts index da6f7d8adb3..74c796f93d6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/index.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/index.ts @@ -28,11 +28,13 @@ import { Hashtag } from './hashtag'; import { hashtagSuggestion } from './hashtag/hashtagSuggestion'; import { Image } from './image/image'; import { LinkExtension } from './link'; +import MathEquation from './MathEquation/MathEquation'; import { Mention } from './mention'; import { mentionSuggestion } from './mention/mentionSuggestions'; import slashCommand from './slash-command'; import { getSuggestionItems } from './slash-command/items'; import renderItems from './slash-command/renderItems'; +import { TrailingNode } from './trailing-node'; export const extensions = [ StarterKit.configure({ @@ -144,4 +146,6 @@ export const extensions = [ 'data-om-table-cell': 'om-table-cell', }, }), + MathEquation, + TrailingNode, ]; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/link.ts b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/link.ts index 18429e57c94..3282e1527de 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/link.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/link.ts @@ -10,8 +10,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { markInputRule, markPasteRule } from '@tiptap/core'; +import { + InputRule, + markInputRule, + markPasteRule, + PasteRule, +} from '@tiptap/core'; import TipTapLinkExtension from '@tiptap/extension-link'; +import { + LINK_INPUT_REGEX, + LINK_PASTE_REGEX, +} from '../../../constants/BlockEditor.constants'; + +const linkInputRule = (config: Parameters[0]) => { + const defaultMarkInputRule = markInputRule(config); + + return new InputRule({ + find: config.find, + handler(props) { + const { tr } = props.state; + + defaultMarkInputRule.handler(props); + tr.setMeta('preventAutolink', true); + }, + }); +}; + +const linkPasteRule = (config: Parameters[0]) => { + const defaultMarkPasteRule = markPasteRule(config); + + return new PasteRule({ + find: config.find, + handler(props) { + const { tr } = props.state; + + defaultMarkPasteRule.handler(props); + tr.setMeta('preventAutolink', true); + }, + }); +}; export const LinkExtension = TipTapLinkExtension.extend({ addAttributes() { @@ -100,13 +137,15 @@ export const LinkExtension = TipTapLinkExtension.extend({ }, addInputRules() { return [ - markInputRule({ - find: /\[(.*?)\]\((https?:\/\/[^\s)]+)\)/, + ...(this.parent?.() ?? []), + linkInputRule({ + find: LINK_INPUT_REGEX, type: this.type, - getAttributes: (match) => { - const [, text, href] = match; - - return { 'data-textcontent': text, href }; + getAttributes(match) { + return { + title: match.pop()?.trim(), + href: match.pop()?.trim(), + }; }, }), ]; @@ -114,13 +153,14 @@ export const LinkExtension = TipTapLinkExtension.extend({ addPasteRules() { return [ ...(this.parent?.() ?? []), - markPasteRule({ - find: /\[(.*?)\]\((https?:\/\/[^\s)]+)\)/, + linkPasteRule({ + find: LINK_PASTE_REGEX, type: this.type, - getAttributes: (match) => { - const [, text, href] = match; - - return { 'data-textcontent': text, href }; + getAttributes(match) { + return { + title: match.pop()?.trim(), + href: match.pop()?.trim(), + }; }, }), ]; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/slash-command/items.ts b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/slash-command/items.ts index d14b87ce554..0b41c1307af 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/slash-command/items.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/slash-command/items.ts @@ -12,6 +12,7 @@ */ import { Editor, Range } from '@tiptap/core'; import HashtagImage from '../../../../assets/img/ic-format-hashtag.png'; +import MathEquationImage from '../../../../assets/img/ic-format-math-equation.png'; import BulletListImage from '../../../../assets/img/ic-slash-bullet-list.png'; import DividerImage from '../../../../assets/img/ic-slash-divider.png'; import H1Image from '../../../../assets/img/ic-slash-h1.png'; @@ -232,6 +233,27 @@ export const getSuggestionItems = (props: { type: SuggestionItemType.ADVANCED_BLOCKS, imgSrc: IconTable, }, + { + title: 'Math Equation', + description: 'Add a math equation', + searchTerms: ['math', 'equation', 'latex', 'katex'], + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .insertContent({ + type: 'MathEquation', + attrs: { + isEditing: true, + math_equation: '', + }, + }) + .run(); + }, + type: SuggestionItemType.ADVANCED_BLOCKS, + imgSrc: MathEquationImage, + }, ]; const filteredItems = suggestionItems.filter((item) => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/trailing-node.ts b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/trailing-node.ts new file mode 100644 index 00000000000..96666a9d1f6 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/trailing-node.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2024 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 { Extension } from '@tiptap/core'; +import { Plugin, PluginKey } from '@tiptap/pm/state'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function nodeEqualsType({ types, node }: any) { + return ( + (Array.isArray(types) && types.includes(node.type)) || node.type === types + ); +} + +export interface TrailingNodeOptions { + node: string; + notAfter: string[]; +} + +export const TrailingNode = Extension.create({ + name: 'trailingNode', + + addOptions() { + return { + node: 'paragraph', + notAfter: ['paragraph'], + }; + }, + + addProseMirrorPlugins() { + const plugin = new PluginKey(this.name); + const disabledNodes = Object.entries(this.editor.schema.nodes) + .map(([, value]) => value) + .filter((node) => this.options.notAfter.includes(node.name)); + + return [ + new Plugin({ + key: plugin, + appendTransaction: (_, __, state) => { + const { doc, tr, schema } = state; + const shouldInsertNodeAtEnd = plugin.getState(state); + const endPosition = doc.content.size; + const type = schema.nodes[this.options.node]; + + if (!shouldInsertNodeAtEnd) { + return; + } + + // eslint-disable-next-line consistent-return + return tr.insert(endPosition, type.create()); + }, + state: { + init: (_, state) => { + const lastNode = state.tr.doc.lastChild; + + return !nodeEqualsType({ node: lastNode, types: disabledNodes }); + }, + apply: (tr, value) => { + if (!tr.docChanged) { + return value; + } + + const lastNode = tr.doc.lastChild; + + return !nodeEqualsType({ node: lastNode, types: disabledNodes }); + }, + }, + }), + ]; + }, +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/LinkPopup/LinkPopup.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/LinkPopup/LinkPopup.tsx index 2c8c9d6b6fc..eccc9815806 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/LinkPopup/LinkPopup.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/LinkPopup/LinkPopup.tsx @@ -41,7 +41,7 @@ const LinkPopup: FC = ({