mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-03 06:03:12 +00:00
* 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
This commit is contained in:
parent
9a31a35296
commit
bf1dddedeb
@ -96,6 +96,7 @@
|
|||||||
"i18next": "^21.10.0",
|
"i18next": "^21.10.0",
|
||||||
"i18next-browser-languagedetector": "^6.1.6",
|
"i18next-browser-languagedetector": "^6.1.6",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
|
"katex": "^0.16.10",
|
||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
"less-loader": "^11.0.0",
|
"less-loader": "^11.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@ -119,6 +120,7 @@
|
|||||||
"react-grid-layout": "^1.4.2",
|
"react-grid-layout": "^1.4.2",
|
||||||
"react-helmet-async": "^1.3.0",
|
"react-helmet-async": "^1.3.0",
|
||||||
"react-i18next": "^11.18.6",
|
"react-i18next": "^11.18.6",
|
||||||
|
"react-latex-next": "^3.0.0",
|
||||||
"react-lazylog": "^4.5.3",
|
"react-lazylog": "^4.5.3",
|
||||||
"react-oidc": "^1.0.3",
|
"react-oidc": "^1.0.3",
|
||||||
"react-papaparse": "^4.1.0",
|
"react-papaparse": "^4.1.0",
|
||||||
@ -169,6 +171,7 @@
|
|||||||
"@types/diff": "^5.0.2",
|
"@types/diff": "^5.0.2",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
|
"@types/katex": "^0.16.7",
|
||||||
"@types/lodash": "^4.14.167",
|
"@types/lodash": "^4.14.167",
|
||||||
"@types/luxon": "^3.0.1",
|
"@types/luxon": "^3.0.1",
|
||||||
"@types/moment": "^2.13.0",
|
"@types/moment": "^2.13.0",
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
@ -35,10 +35,6 @@ const EditorSlots = forwardRef<EditorSlotsRef, EditorSlotsProps>(
|
|||||||
|
|
||||||
const handleLinkCancel = () => {
|
const handleLinkCancel = () => {
|
||||||
handleLinkToggle();
|
handleLinkToggle();
|
||||||
if (!isNil(editor)) {
|
|
||||||
editor.chain().focus().extendMarkRange('link').unsetLink().run();
|
|
||||||
editor.chain().blur().run();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLinkSave = (values: LinkData, op: 'edit' | 'add') => {
|
const handleLinkSave = (values: LinkData, op: 'edit' | 'add') => {
|
||||||
@ -98,8 +94,11 @@ const EditorSlots = forwardRef<EditorSlotsRef, EditorSlotsProps>(
|
|||||||
|
|
||||||
return;
|
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, {
|
component = new ReactRenderer(LinkPopup, {
|
||||||
editor: editor as Editor,
|
editor: editor as Editor,
|
||||||
|
@ -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],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
@ -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<NodeViewProps> = ({
|
||||||
|
node,
|
||||||
|
updateAttributes,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const inputRef = React.useRef<TextAreaRef>(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 (
|
||||||
|
<NodeViewWrapper className="block-math-equation">
|
||||||
|
<div
|
||||||
|
className={classNames('math-equation-wrapper', {
|
||||||
|
isediting: isEditing,
|
||||||
|
})}>
|
||||||
|
{isEditing ? (
|
||||||
|
<div className="math-equation-edit-input-wrapper">
|
||||||
|
<Input.TextArea
|
||||||
|
autoFocus
|
||||||
|
bordered={false}
|
||||||
|
className="math-equation-input"
|
||||||
|
defaultValue={equation}
|
||||||
|
placeholder='Enter your equation here. For example: "x^2 + y^2 = z^2"'
|
||||||
|
ref={inputRef}
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
<Space direction="horizontal" size={8}>
|
||||||
|
<Button
|
||||||
|
icon={<CloseOutlined />}
|
||||||
|
size="small"
|
||||||
|
type="default"
|
||||||
|
onClick={() => setIsEditing(false)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon={<CheckOutlined />}
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
onClick={handleSaveEquation}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Latex>{equation}</Latex>
|
||||||
|
)}
|
||||||
|
{!isEditing && (
|
||||||
|
<Tooltip
|
||||||
|
title={t('label.edit-entity', { entity: t('label.equation') })}>
|
||||||
|
<Button
|
||||||
|
className="edit-button"
|
||||||
|
icon={<EditIcon width={16} />}
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
onClick={() => setIsEditing(true)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</NodeViewWrapper>
|
||||||
|
);
|
||||||
|
};
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -28,11 +28,13 @@ import { Hashtag } from './hashtag';
|
|||||||
import { hashtagSuggestion } from './hashtag/hashtagSuggestion';
|
import { hashtagSuggestion } from './hashtag/hashtagSuggestion';
|
||||||
import { Image } from './image/image';
|
import { Image } from './image/image';
|
||||||
import { LinkExtension } from './link';
|
import { LinkExtension } from './link';
|
||||||
|
import MathEquation from './MathEquation/MathEquation';
|
||||||
import { Mention } from './mention';
|
import { Mention } from './mention';
|
||||||
import { mentionSuggestion } from './mention/mentionSuggestions';
|
import { mentionSuggestion } from './mention/mentionSuggestions';
|
||||||
import slashCommand from './slash-command';
|
import slashCommand from './slash-command';
|
||||||
import { getSuggestionItems } from './slash-command/items';
|
import { getSuggestionItems } from './slash-command/items';
|
||||||
import renderItems from './slash-command/renderItems';
|
import renderItems from './slash-command/renderItems';
|
||||||
|
import { TrailingNode } from './trailing-node';
|
||||||
|
|
||||||
export const extensions = [
|
export const extensions = [
|
||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
@ -144,4 +146,6 @@ export const extensions = [
|
|||||||
'data-om-table-cell': 'om-table-cell',
|
'data-om-table-cell': 'om-table-cell',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
MathEquation,
|
||||||
|
TrailingNode,
|
||||||
];
|
];
|
||||||
|
@ -10,8 +10,45 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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 TipTapLinkExtension from '@tiptap/extension-link';
|
||||||
|
import {
|
||||||
|
LINK_INPUT_REGEX,
|
||||||
|
LINK_PASTE_REGEX,
|
||||||
|
} from '../../../constants/BlockEditor.constants';
|
||||||
|
|
||||||
|
const linkInputRule = (config: Parameters<typeof markInputRule>[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<typeof markPasteRule>[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({
|
export const LinkExtension = TipTapLinkExtension.extend({
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
@ -100,13 +137,15 @@ export const LinkExtension = TipTapLinkExtension.extend({
|
|||||||
},
|
},
|
||||||
addInputRules() {
|
addInputRules() {
|
||||||
return [
|
return [
|
||||||
markInputRule({
|
...(this.parent?.() ?? []),
|
||||||
find: /\[(.*?)\]\((https?:\/\/[^\s)]+)\)/,
|
linkInputRule({
|
||||||
|
find: LINK_INPUT_REGEX,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
getAttributes: (match) => {
|
getAttributes(match) {
|
||||||
const [, text, href] = match;
|
return {
|
||||||
|
title: match.pop()?.trim(),
|
||||||
return { 'data-textcontent': text, href };
|
href: match.pop()?.trim(),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
@ -114,13 +153,14 @@ export const LinkExtension = TipTapLinkExtension.extend({
|
|||||||
addPasteRules() {
|
addPasteRules() {
|
||||||
return [
|
return [
|
||||||
...(this.parent?.() ?? []),
|
...(this.parent?.() ?? []),
|
||||||
markPasteRule({
|
linkPasteRule({
|
||||||
find: /\[(.*?)\]\((https?:\/\/[^\s)]+)\)/,
|
find: LINK_PASTE_REGEX,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
getAttributes: (match) => {
|
getAttributes(match) {
|
||||||
const [, text, href] = match;
|
return {
|
||||||
|
title: match.pop()?.trim(),
|
||||||
return { 'data-textcontent': text, href };
|
href: match.pop()?.trim(),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
import { Editor, Range } from '@tiptap/core';
|
import { Editor, Range } from '@tiptap/core';
|
||||||
import HashtagImage from '../../../../assets/img/ic-format-hashtag.png';
|
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 BulletListImage from '../../../../assets/img/ic-slash-bullet-list.png';
|
||||||
import DividerImage from '../../../../assets/img/ic-slash-divider.png';
|
import DividerImage from '../../../../assets/img/ic-slash-divider.png';
|
||||||
import H1Image from '../../../../assets/img/ic-slash-h1.png';
|
import H1Image from '../../../../assets/img/ic-slash-h1.png';
|
||||||
@ -232,6 +233,27 @@ export const getSuggestionItems = (props: {
|
|||||||
type: SuggestionItemType.ADVANCED_BLOCKS,
|
type: SuggestionItemType.ADVANCED_BLOCKS,
|
||||||
imgSrc: IconTable,
|
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) => {
|
const filteredItems = suggestionItems.filter((item) => {
|
||||||
|
@ -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<TrailingNodeOptions>({
|
||||||
|
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 });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
@ -41,7 +41,7 @@ const LinkPopup: FC<LinkPopupProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
className="p-0"
|
className="p-0"
|
||||||
href={href}
|
href={href}
|
||||||
icon={<ExternalLinkIcon width={iconSize} />}
|
icon={<ExternalLinkIcon width={iconSize + 2} />}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
type="link"
|
type="link"
|
||||||
/>
|
/>
|
||||||
|
@ -268,14 +268,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.link-popup {
|
.link-popup {
|
||||||
|
align-items: center;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
background: @white;
|
background: @white;
|
||||||
border: @global-border;
|
border: @global-border;
|
||||||
border-radius: 20px;
|
border-radius: @border-radius-base;
|
||||||
|
|
||||||
button,
|
button,
|
||||||
a {
|
a {
|
||||||
color: @text-color !important;
|
color: @text-color !important;
|
||||||
|
&:hover {
|
||||||
|
background-color: @hover-bg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ export const EDITOR_OPTIONS: Partial<EditorOptions> = {
|
|||||||
'image',
|
'image',
|
||||||
'taskItem',
|
'taskItem',
|
||||||
'callout',
|
'callout',
|
||||||
|
'link',
|
||||||
|
'MathEquation',
|
||||||
],
|
],
|
||||||
parseOptions: {
|
parseOptions: {
|
||||||
preserveWhitespace: 'full',
|
preserveWhitespace: 'full',
|
||||||
@ -63,3 +65,9 @@ export const CALLOUT_CONTENT = {
|
|||||||
|
|
||||||
export const CALL_OUT_REGEX = /^:::([A-Za-z]*)?$/;
|
export const CALL_OUT_REGEX = /^:::([A-Za-z]*)?$/;
|
||||||
export const CALL_OUT_INPUT_RULE_REGEX = /^::: $/;
|
export const CALL_OUT_INPUT_RULE_REGEX = /^::: $/;
|
||||||
|
|
||||||
|
export const LINK_INPUT_REGEX =
|
||||||
|
/(?:^|\s)\[([^\]]*)?\]\((\S+)(?: ["“](.+)["”])?\)$/i;
|
||||||
|
|
||||||
|
export const LINK_PASTE_REGEX =
|
||||||
|
/(?:^|\s)\[([^\]]*)?\]\((\S+)(?: ["“](.+)["”])?\)/gi;
|
||||||
|
@ -429,6 +429,7 @@
|
|||||||
"entity-type-plural": "{{entity}}-Typen",
|
"entity-type-plural": "{{entity}}-Typen",
|
||||||
"entity-version-detail-plural": "Details zu {{entity}}-Versionen",
|
"entity-version-detail-plural": "Details zu {{entity}}-Versionen",
|
||||||
"enum-value-plural": "Enum Values",
|
"enum-value-plural": "Enum Values",
|
||||||
|
"equation": "Equation",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"error-plural": "Errors",
|
"error-plural": "Errors",
|
||||||
"event-publisher-plural": "Ereignisveröffentlicher",
|
"event-publisher-plural": "Ereignisveröffentlicher",
|
||||||
|
@ -429,6 +429,7 @@
|
|||||||
"entity-type-plural": "{{entity}} Type",
|
"entity-type-plural": "{{entity}} Type",
|
||||||
"entity-version-detail-plural": "{{entity}} Version Details",
|
"entity-version-detail-plural": "{{entity}} Version Details",
|
||||||
"enum-value-plural": "Enum Values",
|
"enum-value-plural": "Enum Values",
|
||||||
|
"equation": "Equation",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"error-plural": "Errors",
|
"error-plural": "Errors",
|
||||||
"event-publisher-plural": "Event Publishers",
|
"event-publisher-plural": "Event Publishers",
|
||||||
|
@ -429,6 +429,7 @@
|
|||||||
"entity-type-plural": "Tipo de {{entity}}",
|
"entity-type-plural": "Tipo de {{entity}}",
|
||||||
"entity-version-detail-plural": "Detalles de versión de {{entity}}",
|
"entity-version-detail-plural": "Detalles de versión de {{entity}}",
|
||||||
"enum-value-plural": "Valores de enumeración",
|
"enum-value-plural": "Valores de enumeración",
|
||||||
|
"equation": "Equation",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"error-plural": "Errores",
|
"error-plural": "Errores",
|
||||||
"event-publisher-plural": "Publicadores de eventos",
|
"event-publisher-plural": "Publicadores de eventos",
|
||||||
|
@ -429,6 +429,7 @@
|
|||||||
"entity-type-plural": "{{entity}} Types",
|
"entity-type-plural": "{{entity}} Types",
|
||||||
"entity-version-detail-plural": "Détails des Versions de {{entity}}",
|
"entity-version-detail-plural": "Détails des Versions de {{entity}}",
|
||||||
"enum-value-plural": "Enum Values",
|
"enum-value-plural": "Enum Values",
|
||||||
|
"equation": "Equation",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"error-plural": "Errors",
|
"error-plural": "Errors",
|
||||||
"event-publisher-plural": "Publicateurs d'Événements",
|
"event-publisher-plural": "Publicateurs d'Événements",
|
||||||
|
@ -429,6 +429,7 @@
|
|||||||
"entity-type-plural": "סוגי {{entity}}",
|
"entity-type-plural": "סוגי {{entity}}",
|
||||||
"entity-version-detail-plural": "גרסאות פרטי {{entity}}",
|
"entity-version-detail-plural": "גרסאות פרטי {{entity}}",
|
||||||
"enum-value-plural": "Enum Values",
|
"enum-value-plural": "Enum Values",
|
||||||
|
"equation": "Equation",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"error-plural": "Errors",
|
"error-plural": "Errors",
|
||||||
"event-publisher-plural": "מפרסמי אירועים",
|
"event-publisher-plural": "מפרסמי אירועים",
|
||||||
|
@ -429,6 +429,7 @@
|
|||||||
"entity-type-plural": "{{entity}} Type",
|
"entity-type-plural": "{{entity}} Type",
|
||||||
"entity-version-detail-plural": "{{entity}} Version Details",
|
"entity-version-detail-plural": "{{entity}} Version Details",
|
||||||
"enum-value-plural": "Enum Values",
|
"enum-value-plural": "Enum Values",
|
||||||
|
"equation": "Equation",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"error-plural": "Errors",
|
"error-plural": "Errors",
|
||||||
"event-publisher-plural": "イベントの作成者",
|
"event-publisher-plural": "イベントの作成者",
|
||||||
|
@ -429,6 +429,7 @@
|
|||||||
"entity-type-plural": "{{entity}}-type",
|
"entity-type-plural": "{{entity}}-type",
|
||||||
"entity-version-detail-plural": "{{entity}}-versie-details",
|
"entity-version-detail-plural": "{{entity}}-versie-details",
|
||||||
"enum-value-plural": "Enum Values",
|
"enum-value-plural": "Enum Values",
|
||||||
|
"equation": "Equation",
|
||||||
"error": "Fout",
|
"error": "Fout",
|
||||||
"error-plural": "Errors",
|
"error-plural": "Errors",
|
||||||
"event-publisher-plural": "Gebeurtenis-publisher",
|
"event-publisher-plural": "Gebeurtenis-publisher",
|
||||||
|
@ -429,6 +429,7 @@
|
|||||||
"entity-type-plural": "Tipo de {{entity}}",
|
"entity-type-plural": "Tipo de {{entity}}",
|
||||||
"entity-version-detail-plural": "Detalhes da Versão de {{entity}}",
|
"entity-version-detail-plural": "Detalhes da Versão de {{entity}}",
|
||||||
"enum-value-plural": "Enum Values",
|
"enum-value-plural": "Enum Values",
|
||||||
|
"equation": "Equation",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"error-plural": "Errors",
|
"error-plural": "Errors",
|
||||||
"event-publisher-plural": "Publicadores de Eventos",
|
"event-publisher-plural": "Publicadores de Eventos",
|
||||||
|
@ -429,6 +429,7 @@
|
|||||||
"entity-type-plural": "Тип {{entity}}",
|
"entity-type-plural": "Тип {{entity}}",
|
||||||
"entity-version-detail-plural": "{{entity}} Version Details",
|
"entity-version-detail-plural": "{{entity}} Version Details",
|
||||||
"enum-value-plural": "Enum Values",
|
"enum-value-plural": "Enum Values",
|
||||||
|
"equation": "Equation",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"error-plural": "Errors",
|
"error-plural": "Errors",
|
||||||
"event-publisher-plural": "Издатели события",
|
"event-publisher-plural": "Издатели события",
|
||||||
|
@ -429,6 +429,7 @@
|
|||||||
"entity-type-plural": "{{entity}}类型",
|
"entity-type-plural": "{{entity}}类型",
|
||||||
"entity-version-detail-plural": "{{entity}}版本详情",
|
"entity-version-detail-plural": "{{entity}}版本详情",
|
||||||
"enum-value-plural": "Enum Values",
|
"enum-value-plural": "Enum Values",
|
||||||
|
"equation": "Equation",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"error-plural": "Errors",
|
"error-plural": "Errors",
|
||||||
"event-publisher-plural": "事件发布者",
|
"event-publisher-plural": "事件发布者",
|
||||||
|
@ -95,6 +95,7 @@ module.exports = {
|
|||||||
path.resolve(__dirname, 'node_modules/react-toastify'),
|
path.resolve(__dirname, 'node_modules/react-toastify'),
|
||||||
path.resolve(__dirname, 'node_modules/quill-emoji'),
|
path.resolve(__dirname, 'node_modules/quill-emoji'),
|
||||||
path.resolve(__dirname, 'node_modules/react-awesome-query-builder'),
|
path.resolve(__dirname, 'node_modules/react-awesome-query-builder'),
|
||||||
|
path.resolve(__dirname, 'node_modules/katex'),
|
||||||
],
|
],
|
||||||
// May need to handle files outside the source code
|
// May need to handle files outside the source code
|
||||||
// (from node_modules)
|
// (from node_modules)
|
||||||
|
@ -96,6 +96,7 @@ module.exports = {
|
|||||||
path.resolve(__dirname, 'node_modules/react-toastify'),
|
path.resolve(__dirname, 'node_modules/react-toastify'),
|
||||||
path.resolve(__dirname, 'node_modules/quill-emoji'),
|
path.resolve(__dirname, 'node_modules/quill-emoji'),
|
||||||
path.resolve(__dirname, 'node_modules/react-awesome-query-builder'),
|
path.resolve(__dirname, 'node_modules/react-awesome-query-builder'),
|
||||||
|
path.resolve(__dirname, 'node_modules/katex'),
|
||||||
],
|
],
|
||||||
// May need to handle files outside the source code
|
// May need to handle files outside the source code
|
||||||
// (from node_modules)
|
// (from node_modules)
|
||||||
|
@ -4258,6 +4258,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||||
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
||||||
|
|
||||||
|
"@types/katex@^0.16.7":
|
||||||
|
version "0.16.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.16.7.tgz#03ab680ab4fa4fbc6cb46ecf987ecad5d8019868"
|
||||||
|
integrity sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==
|
||||||
|
|
||||||
"@types/lodash@^4.14.167":
|
"@types/lodash@^4.14.167":
|
||||||
version "4.14.172"
|
version "4.14.172"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
|
||||||
@ -10241,6 +10246,13 @@ jwt-decode@^3.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
|
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
|
||||||
integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==
|
integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==
|
||||||
|
|
||||||
|
katex@^0.16.0, katex@^0.16.10:
|
||||||
|
version "0.16.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.10.tgz#6f81b71ac37ff4ec7556861160f53bc5f058b185"
|
||||||
|
integrity sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==
|
||||||
|
dependencies:
|
||||||
|
commander "^8.3.0"
|
||||||
|
|
||||||
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
|
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
|
||||||
version "3.2.2"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||||
@ -12824,6 +12836,13 @@ react-is@^18.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||||
|
|
||||||
|
react-latex-next@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-latex-next/-/react-latex-next-3.0.0.tgz#3e347a13b1e701439b5fa52f75201bc86166854e"
|
||||||
|
integrity sha512-x70f1b1G7TronVigsRgKHKYYVUNfZk/3bciFyYX1lYLQH2y3/TXku3+5Vap8MDbJhtopePSYBsYWS6jhzIdz+g==
|
||||||
|
dependencies:
|
||||||
|
katex "^0.16.0"
|
||||||
|
|
||||||
react-lazylog@^4.5.3:
|
react-lazylog@^4.5.3:
|
||||||
version "4.5.3"
|
version "4.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-lazylog/-/react-lazylog-4.5.3.tgz#289e24995b5599e75943556ac63f5e2c04d0001e"
|
resolved "https://registry.yarnpkg.com/react-lazylog/-/react-lazylog-4.5.3.tgz#289e24995b5599e75943556ac63f5e2c04d0001e"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user