mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-01 19:18:05 +00:00
feat(ui): add support for callout in block editor (#13839)
* feat(ui): add support for callout in block editor * remove margin * use variables for colors * center align the text * chore(ui): improve styling and icons
This commit is contained in:
parent
335aa86fcc
commit
7b12ad425f
@ -0,0 +1,15 @@
|
||||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7120_65403)">
|
||||
<path d="M20.3101 6H18.7501C18.2801 6 17.9801 6.49 18.1801 6.91L19.9501 10.54C20.1001 10.84 20.5301 10.84 20.6801 10.54L22.4501 6.91C22.6501 6.49 22.3501 6 21.8801 6H20.3201H20.3101Z" fill="#F75E5E"/>
|
||||
<path d="M11.6901 8.54996L10.4701 9.52996C10.1101 9.81996 10.1801 10.39 10.6001 10.59L14.2601 12.3C14.5701 12.44 14.9001 12.17 14.8301 11.84L13.9201 7.90996C13.8201 7.45996 13.2701 7.26996 12.9101 7.55996L11.6901 8.53996V8.54996Z" fill="#F75E5E"/>
|
||||
<path d="M28.93 8.54996L30.15 9.52996C30.51 9.81996 30.44 10.39 30.02 10.59L26.36 12.3C26.05 12.44 25.72 12.17 25.79 11.84L26.7 7.90996C26.8 7.45996 27.35 7.26996 27.71 7.55996L28.93 8.53996V8.54996Z" fill="#F75E5E"/>
|
||||
<path d="M30.5201 30.5901H10.0901L10.8501 18.3401C11.0101 15.7901 13.1201 13.8101 15.6701 13.8101H24.9501C27.5001 13.8101 29.6101 15.7901 29.7701 18.3401L30.5301 30.5901H30.5201Z" fill="#EA322D"/>
|
||||
<path d="M31.37 28.77H9.24C8.55517 28.77 8 29.3252 8 30.01V31.93C8 32.6149 8.55517 33.17 9.24 33.17H31.37C32.0548 33.17 32.61 32.6149 32.61 31.93V30.01C32.61 29.3252 32.0548 28.77 31.37 28.77Z" fill="#2F3846"/>
|
||||
<path d="M26.2901 21.51C26.2901 18.21 23.6101 15.53 20.3101 15.53C17.0101 15.53 14.3301 18.21 14.3301 21.51C14.3301 24.81 17.0101 27.49 20.3101 27.49C23.6101 27.49 26.2901 24.81 26.2901 21.51Z" fill="#F75E5E"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7120_65403">
|
||||
<rect width="24.61" height="27.17" fill="white" transform="translate(8 6)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,19 @@
|
||||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7120_65405)">
|
||||
<path d="M29.22 15.11C29.22 10.07 25.13 6 20.09 6C15.24 6 11.2 9.9 11 14.75C10.87 17.96 12.42 20.83 14.83 22.54C15.56 23.06 15.98 23.91 15.98 24.8V25.54C15.98 26.18 16.5 26.7 17.14 26.7H23.08C23.72 26.7 24.24 26.18 24.24 25.54V24.8C24.24 23.88 24.7 23.03 25.45 22.49C27.74 20.84 29.23 18.15 29.23 15.11H29.22Z" fill="#FFD983"/>
|
||||
<path d="M17.22 26.7H23V28.1C23 28.48 22.69 28.79 22.31 28.79H17.91C17.53 28.79 17.22 28.48 17.22 28.1V26.7Z" fill="#DCE2E2"/>
|
||||
<path d="M22.2699 28.78H17.9399V29.31H22.2699V28.78Z" fill="#C8CECE"/>
|
||||
<path d="M22.56 30.1799H17.65C17.41 30.1799 17.22 29.9899 17.22 29.7499C17.22 29.5099 17.41 29.3199 17.65 29.3199H22.56C22.8 29.3199 22.99 29.5099 22.99 29.7499C22.99 29.9899 22.8 30.1799 22.56 30.1799Z" fill="#DCE2E2"/>
|
||||
<path d="M22.2699 30.1801H17.9399V30.7101H22.2699V30.1801Z" fill="#C8CECE"/>
|
||||
<path d="M20.11 33.1699H20.23C20.69 33.1699 21.14 33.0199 21.5 32.7299L23 31.5699H17.22L18.72 32.7299C19.08 33.0099 19.53 33.1699 19.99 33.1699H20.11Z" fill="#6E7777"/>
|
||||
<path d="M23 31.57V30.9699C23 30.8199 22.88 30.7 22.73 30.7H17.49C17.34 30.7 17.22 30.8199 17.22 30.9699V31.57H23Z" fill="#DCE2E2"/>
|
||||
<path d="M21.0301 21.5C21.0301 21.5 20.9601 21.5 20.9301 21.47C20.8301 21.41 20.8001 21.29 20.8601 21.19L23.8101 16.1C23.6501 16.26 23.4601 16.38 23.1801 16.38C22.6801 16.38 22.4401 15.99 22.2401 15.67C22.0401 15.35 21.9001 15.15 21.6501 15.15C21.4001 15.15 21.2601 15.35 21.0601 15.67C20.8601 15.99 20.6201 16.38 20.1201 16.38C19.6201 16.38 19.3801 15.99 19.1801 15.67C18.9801 15.35 18.8401 15.15 18.5901 15.15C18.3401 15.15 18.2001 15.35 18.0001 15.67C17.8001 15.99 17.5601 16.38 17.0601 16.38C16.7901 16.38 16.5901 16.26 16.4301 16.1L19.3801 21.19C19.4401 21.29 19.4001 21.41 19.3101 21.47C19.2101 21.53 19.0901 21.49 19.0301 21.4L15.3601 15.05C15.3201 14.99 15.3201 14.91 15.3601 14.84C15.4001 14.78 15.4601 14.74 15.5401 14.74C16.0401 14.74 16.2801 15.13 16.4801 15.45C16.6801 15.77 16.8201 15.97 17.0701 15.97C17.3201 15.97 17.4601 15.77 17.6601 15.45C17.8601 15.13 18.1001 14.74 18.6001 14.74C19.1001 14.74 19.3401 15.13 19.5401 15.45C19.7401 15.77 19.8801 15.97 20.1301 15.97C20.3801 15.97 20.5201 15.77 20.7201 15.45C20.9201 15.13 21.1601 14.74 21.6601 14.74C22.1601 14.74 22.4001 15.13 22.6001 15.45C22.8001 15.77 22.9401 15.97 23.1901 15.97C23.4401 15.97 23.5801 15.77 23.7801 15.45C23.9801 15.13 24.2201 14.74 24.7201 14.74C24.7901 14.74 24.8601 14.78 24.9001 14.84C24.9401 14.9 24.9401 14.98 24.9001 15.05L21.2301 21.4C21.1901 21.47 21.1201 21.5 21.0501 21.5H21.0301Z" fill="#FFA100"/>
|
||||
<path d="M20.79 20.52V18.67C20.79 18.31 20.5 18.01 20.13 18.01H20.09C19.73 18.01 19.43 18.3 19.43 18.67V20.52C19.05 20.52 18.75 20.82 18.75 21.2V26.7H21.47V21.2C21.47 20.82 21.17 20.52 20.79 20.52Z" fill="#FFEDCA"/>
|
||||
<path d="M21.8999 9.72003C21.8999 10.72 22.7099 11.53 23.7099 11.53C24.7099 11.53 25.5199 10.72 25.5199 9.72003C25.5199 8.72003 24.7099 7.91003 23.7099 7.91003C22.7099 7.91003 21.8999 8.72003 21.8999 9.72003Z" fill="#FFEDCA"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7120_65405">
|
||||
<rect width="18.22" height="27.17" fill="white" transform="translate(11 6)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@ -0,0 +1,23 @@
|
||||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7120_65404)">
|
||||
<path d="M9.19 33.17H27.74C28.39 33.17 28.93 32.64 28.93 31.98V12.12L27.03 9.06L21.65 6H9.19C8.53 6 8 6.53 8 7.19V31.99C8 32.64 8.53 33.18 9.19 33.18V33.17Z" fill="#E8EDED"/>
|
||||
<path d="M24.7901 15.9399H12.1401C11.8301 15.9399 11.5801 15.6899 11.5801 15.3799C11.5801 15.0699 11.8301 14.8199 12.1401 14.8199H24.7901C25.1001 14.8199 25.3501 15.0699 25.3501 15.3799C25.3501 15.6899 25.1001 15.9399 24.7901 15.9399Z" fill="#B3B9BA"/>
|
||||
<path d="M24.7901 19.8899H12.1401C11.8301 19.8899 11.5801 19.6399 11.5801 19.3299C11.5801 19.0199 11.8301 18.7699 12.1401 18.7699H24.7901C25.1001 18.7699 25.3501 19.0199 25.3501 19.3299C25.3501 19.6399 25.1001 19.8899 24.7901 19.8899Z" fill="#B3B9BA"/>
|
||||
<path d="M24.7901 23.84H12.1401C11.8301 23.84 11.5801 23.59 11.5801 23.28C11.5801 22.97 11.8301 22.72 12.1401 22.72H24.7901C25.1001 22.72 25.3501 22.97 25.3501 23.28C25.3501 23.59 25.1001 23.84 24.7901 23.84Z" fill="#B3B9BA"/>
|
||||
<path d="M18.6101 27.7899H12.1301C11.8201 27.7899 11.5701 27.5399 11.5701 27.2299C11.5701 26.9199 11.8201 26.6699 12.1301 26.6699H18.6101C18.9201 26.6699 19.1701 26.9199 19.1701 27.2299C19.1701 27.5399 18.9201 27.7899 18.6101 27.7899Z" fill="#B3B9BA"/>
|
||||
<path d="M28.9201 12.12H22.6201C22.2201 12.12 21.9001 11.8 21.9001 11.4V8.32C21.9001 7.04 20.8601 6 19.5801 6H22.8001C26.1801 6 28.9201 8.74 28.9201 12.12Z" fill="#D2D8D8"/>
|
||||
<path d="M18.0599 25.7599L16.4199 30.2599C16.2299 30.7899 16.7399 31.3099 17.2799 31.1199L21.7799 29.4799L20.7299 26.8099L18.0599 25.7599Z" fill="#FCE3C0"/>
|
||||
<path d="M25.8598 17.97L19.6498 24.18L18.4498 25.38L18.0698 25.76C18.3798 26.07 18.0798 26.37 18.3898 26.68C18.6998 26.99 18.9998 26.69 19.3098 27C19.6198 27.31 19.3198 27.61 19.6298 27.92C19.9398 28.23 20.2398 27.93 20.5498 28.24C20.8598 28.55 20.5598 28.85 20.8698 29.16C21.1798 29.47 21.4798 29.17 21.7898 29.48L22.1698 29.1L23.3698 27.9L29.5798 21.69L25.8598 17.97Z" fill="#FFCA28"/>
|
||||
<path d="M25.8598 17.97L19.6498 24.18L18.4498 25.38L18.0698 25.76C18.3798 26.07 18.0798 26.37 18.3898 26.68C18.6998 26.99 18.9998 26.69 19.3098 27L27.1098 19.2L25.8698 17.96L25.8598 17.97Z" fill="#FFD95F"/>
|
||||
<path d="M20.55 28.25C20.86 28.56 20.56 28.86 20.87 29.17C21.18 29.48 21.48 29.18 21.79 29.49L22.17 29.11L23.37 27.91L29.58 21.7L28.34 20.46L20.54 28.26L20.55 28.25Z" fill="#FFB229"/>
|
||||
<path d="M31.4602 19.9174L27.6347 16.092C27.5098 15.967 27.3071 15.967 27.1822 16.092L25.8104 17.4638C25.6854 17.5888 25.6854 17.7914 25.8104 17.9163L29.6358 21.7418C29.7608 21.8667 29.9634 21.8667 30.0884 21.7418L31.4602 20.37C31.5851 20.245 31.5851 20.0424 31.4602 19.9174Z" fill="#E5EAEA"/>
|
||||
<path d="M29.7719 15.4083L32.1548 17.7913C32.5225 18.159 32.5225 18.7671 32.1548 19.1348L31.4123 19.8772L27.693 16.1579L28.4354 15.4154C28.8031 15.0477 29.4112 15.0477 29.7789 15.4154L29.7719 15.4083Z" fill="#FF5E5A"/>
|
||||
<path d="M31.7851 19.4751L28.0657 15.7557L27.6838 16.1376L31.4032 19.857L31.7851 19.4751Z" fill="#E23B3B"/>
|
||||
<path d="M16.8499 29.1L16.4299 30.26C16.2399 30.79 16.7499 31.31 17.2899 31.12L18.4499 30.7L16.8499 29.1Z" fill="#434849"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7120_65404">
|
||||
<rect width="24.43" height="27.17" fill="white" transform="translate(8 6)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.82007 34.0625C6.27319 34.0625 5.57007 32.9375 6.27319 31.5781L18.7888 6.96875C19.4919 5.60938 20.6169 5.60938 21.3201 6.96875L33.8357 31.5781C34.5388 32.9375 33.8357 34.0625 32.2888 34.0625H7.82007Z" fill="#FFCE31"/>
|
||||
<path d="M18.009 16.0625L19.3215 24.7344C19.4622 25.5781 20.5403 25.5781 20.6809 24.7344L21.9465 16.0625C22.1809 12.6875 17.7747 12.6875 18.009 16.0625Z" fill="#231F20"/>
|
||||
<path d="M19.9778 30.2187C21.0651 30.2187 21.9465 29.3373 21.9465 28.25C21.9465 27.1627 21.0651 26.2812 19.9778 26.2812C18.8905 26.2812 18.009 27.1627 18.009 28.25C18.009 29.3373 18.8905 30.2187 19.9778 30.2187Z" fill="#231F20"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 710 B |
@ -0,0 +1 @@
|
||||
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path fill="none" d="M0 0h24v24H0z"></path><path d="M3.998 21A.996.996 0 0 1 3 20.007V3.993C3 3.445 3.445 3 3.993 3h16.014c.548 0 .993.447.993.999V16l-5.003 5H3.998zM5 19h10.169L19 15.171V5H5v14z"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 336 B |
@ -0,0 +1,249 @@
|
||||
/*
|
||||
* 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 { Fragment, Slice } from 'prosemirror-model';
|
||||
import { TextSelection } from 'prosemirror-state';
|
||||
|
||||
import {
|
||||
findParentNode,
|
||||
isTextSelection,
|
||||
mergeAttributes,
|
||||
Node,
|
||||
wrappingInputRule,
|
||||
} from '@tiptap/core';
|
||||
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||
import {
|
||||
CALL_OUT_INPUT_RULE_REGEX,
|
||||
CALL_OUT_REGEX,
|
||||
} from '../../../../constants/BlockEditor.constants';
|
||||
import CalloutComponent from './CalloutComponent';
|
||||
|
||||
export interface CalloutOptions {
|
||||
/**
|
||||
* Custom HTML attributes that should be added to the rendered HTML tag.
|
||||
*/
|
||||
HTMLAttributes: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CalloutAttributes {
|
||||
/**
|
||||
* The calloutType of the callout.
|
||||
*/
|
||||
calloutType?: string;
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
callout: {
|
||||
/**
|
||||
* Toggle a callout node.
|
||||
*
|
||||
* @param attributes
|
||||
* @returns
|
||||
*/
|
||||
toggleCallout: (attributes: CalloutAttributes) => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const Callout = Node.create<CalloutOptions>({
|
||||
name: 'callout',
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
};
|
||||
},
|
||||
|
||||
content: 'block+',
|
||||
|
||||
group: 'block',
|
||||
|
||||
defining: true,
|
||||
|
||||
draggable: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
calloutType: {
|
||||
default: 'note',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: `div[data-type="${this.name}"]`,
|
||||
getAttrs: (node) => {
|
||||
const htmlNode = node as HTMLElement;
|
||||
|
||||
return {
|
||||
calloutType: htmlNode.getAttribute('data-calloutType') ?? '',
|
||||
content: htmlNode.textContent,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes, node }) {
|
||||
return [
|
||||
'div',
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||
'data-type': this.name,
|
||||
'data-calloutType': node.attrs.calloutType,
|
||||
}),
|
||||
0,
|
||||
];
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(CalloutComponent);
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
toggleCallout: (attributes) => (editor) => {
|
||||
return editor.commands.toggleWrap(this.name, attributes);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Enter: ({ editor }) => {
|
||||
const { state, view } = editor;
|
||||
const { selection } = state;
|
||||
|
||||
if (!(isTextSelection(selection) && selection.empty)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { nodeBefore, parent } = selection.$from;
|
||||
|
||||
if (!nodeBefore?.isText || !parent.type.isTextblock) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const regex = CALL_OUT_REGEX;
|
||||
const { text, nodeSize } = nodeBefore;
|
||||
const { textContent } = parent;
|
||||
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const matchesNodeBefore = text.match(regex);
|
||||
const matchesParent = textContent.match(regex);
|
||||
|
||||
if (!matchesNodeBefore || !matchesParent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pos = selection.$from.before();
|
||||
const end = pos + nodeSize + 1;
|
||||
// +1 to account for the extra pos a node takes up
|
||||
|
||||
const { tr } = state;
|
||||
const slice = new Slice(Fragment.from(this.type.create()), 0, 1);
|
||||
tr.replace(pos, end, slice);
|
||||
|
||||
// Set the selection to within the callout
|
||||
tr.setSelection(TextSelection.near(tr.doc.resolve(pos + 1)));
|
||||
view.dispatch(tr);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the backspace key when deleting content.
|
||||
* Aims to stop merging callouts when deleting content in between.
|
||||
*/
|
||||
Backspace: ({ editor }) => {
|
||||
const { state, view } = editor;
|
||||
const { selection } = state;
|
||||
|
||||
// If the selection is not empty, return false
|
||||
// and let other extension handle the deletion.
|
||||
if (!selection.empty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { $from } = selection;
|
||||
|
||||
// If not at the start of current node, no joining will happen
|
||||
if ($from.parentOffset !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const previousPosition = $from.before($from.depth) - 1;
|
||||
|
||||
// If nothing above to join with
|
||||
if (previousPosition < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const previousPos = state.doc.resolve(previousPosition);
|
||||
|
||||
// If resolving previous position fails, bail out
|
||||
if (!previousPos?.parent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const previousNode = previousPos.parent;
|
||||
const parentNode = findParentNode(() => true)(selection);
|
||||
|
||||
if (!parentNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { node, pos, depth } = parentNode;
|
||||
|
||||
// If current node is nested
|
||||
if (depth !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If previous node is a callout, cut current node's content into it
|
||||
if (node.type !== this.type && previousNode.type === this.type) {
|
||||
const { content, nodeSize } = node;
|
||||
const { tr } = state;
|
||||
|
||||
tr.delete(pos, pos + nodeSize);
|
||||
tr.setSelection(
|
||||
TextSelection.near(tr.doc.resolve(previousPosition - 1))
|
||||
);
|
||||
tr.insert(previousPosition - 1, content);
|
||||
|
||||
view.dispatch(tr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
addInputRules() {
|
||||
return [
|
||||
wrappingInputRule({
|
||||
find: CALL_OUT_INPUT_RULE_REGEX,
|
||||
type: this.type,
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 { NodeViewContent, NodeViewProps, NodeViewWrapper } from '@tiptap/react';
|
||||
import { Button, Popover } from 'antd';
|
||||
import { startCase } from 'lodash';
|
||||
import React, { FC, useState } from 'react';
|
||||
import { CALLOUT_CONTENT } from '../../../../constants/BlockEditor.constants';
|
||||
|
||||
const PopoverContent = ({
|
||||
onSelect,
|
||||
}: {
|
||||
onSelect: (calloutType: string) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div className="callout-menu">
|
||||
{Object.entries(CALLOUT_CONTENT).map(([key, CalloutIcon]) => {
|
||||
return (
|
||||
<Button
|
||||
icon={
|
||||
<CalloutIcon style={{ verticalAlign: 'middle' }} width={20} />
|
||||
}
|
||||
key={key}
|
||||
type="text"
|
||||
onClick={() => onSelect(key)}>
|
||||
{startCase(key)}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CalloutComponent: FC<NodeViewProps> = ({
|
||||
node,
|
||||
extension,
|
||||
updateAttributes,
|
||||
}) => {
|
||||
const { calloutType } = node.attrs;
|
||||
const [isPopupVisible, setIsPopupVisible] = useState<boolean>(false);
|
||||
const CallOutIcon =
|
||||
CALLOUT_CONTENT[calloutType as keyof typeof CALLOUT_CONTENT];
|
||||
|
||||
return (
|
||||
<NodeViewWrapper as="div" className="om-react-node">
|
||||
<div
|
||||
className={`om-callout-node om-callout-node-${calloutType}`}
|
||||
data-type={extension.name}>
|
||||
<Popover
|
||||
align={{ targetOffset: [0, 16] }}
|
||||
content={
|
||||
<PopoverContent
|
||||
onSelect={(value) => {
|
||||
updateAttributes({ calloutType: value });
|
||||
setIsPopupVisible(false);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
destroyTooltipOnHide={{ keepParent: false }}
|
||||
open={isPopupVisible}
|
||||
overlayClassName="om-callout-node-popover"
|
||||
placement="bottomRight"
|
||||
showArrow={false}
|
||||
trigger="click"
|
||||
onOpenChange={setIsPopupVisible}>
|
||||
<Button className="callout-type-btn" type="text">
|
||||
<CallOutIcon width={28} />
|
||||
</Button>
|
||||
</Popover>
|
||||
<NodeViewContent className="om-callout-node-content" />
|
||||
</div>
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default CalloutComponent;
|
||||
@ -17,6 +17,7 @@ import TaskList from '@tiptap/extension-task-list';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import { DROP_CURSOR_COLOR } from '../../../constants/BlockEditor.constants';
|
||||
import BlockAndDragDrop from './BlockAndDragDrop/BlockAndDragDrop';
|
||||
import { Callout } from './Callout/Callout';
|
||||
import DiffView from './diff-view';
|
||||
import { Focus } from './focus';
|
||||
import { Hashtag } from './hashtag';
|
||||
@ -113,4 +114,5 @@ export const extensions = [
|
||||
Focus.configure({
|
||||
mode: 'deepest',
|
||||
}),
|
||||
Callout,
|
||||
];
|
||||
|
||||
@ -21,6 +21,7 @@ import NumberedListImage from '../../../../assets/img/ic-slash-numbered-list.png
|
||||
import QuoteImage from '../../../../assets/img/ic-slash-quote.png';
|
||||
import TextImage from '../../../../assets/img/ic-slash-text.png';
|
||||
import TaskListIcon from '../../../../assets/img/ic-task-list.png';
|
||||
import IconFormatCallout from '../../../../assets/svg/ic-format-callout.svg';
|
||||
import CodeBlockImage from '../../../../assets/svg/ic-format-code-block.svg';
|
||||
import IconFormatImage from '../../../../assets/svg/ic-format-image.svg';
|
||||
import MentionImage from '../../../../assets/svg/ic-mentions.svg';
|
||||
@ -205,6 +206,16 @@ export const getSuggestionItems = (props: {
|
||||
imgSrc: TaskListIcon,
|
||||
type: SuggestionItemType.ADVANCED_BLOCKS,
|
||||
},
|
||||
{
|
||||
title: 'Callout',
|
||||
description: 'A simple callout block',
|
||||
searchTerms: ['callout', 'info', 'warning', 'danger', 'note'],
|
||||
command: ({ editor, range }) => {
|
||||
editor.chain().focus().deleteRange(range).toggleCallout({}).run();
|
||||
},
|
||||
type: SuggestionItemType.ADVANCED_BLOCKS,
|
||||
imgSrc: IconFormatCallout,
|
||||
},
|
||||
];
|
||||
|
||||
const filteredItems = suggestionItems.filter((item) => {
|
||||
|
||||
@ -15,6 +15,12 @@
|
||||
@border-radius: 6px;
|
||||
@border-color: #e8e8ed;
|
||||
@markdown-bg-color: #f8f8fa;
|
||||
@hover-bg: #00000005;
|
||||
@callout-bg: #f8f8fa;
|
||||
@callout-warning-bg: #fff3dc;
|
||||
@callout-info-bg: #d1e9ff;
|
||||
@callout-danger-bg: #ff4c3b33;
|
||||
@callout-border: #afafc1;
|
||||
|
||||
.block-editor-wrapper {
|
||||
.om-block-editor > p:last-child {
|
||||
@ -361,3 +367,60 @@
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.om-callout-node {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: @callout-bg;
|
||||
color: @text-color;
|
||||
padding: 1rem 1.5rem;
|
||||
gap: 0.5rem;
|
||||
border-radius: 8px;
|
||||
border-left: 8px solid @callout-border;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.om-callout-node-warning,
|
||||
.om-callout-node-caution {
|
||||
background-color: @callout-warning-bg;
|
||||
border-left-color: @warning-color;
|
||||
}
|
||||
.om-callout-node-tip,
|
||||
.om-callout-node-info {
|
||||
background-color: @callout-info-bg;
|
||||
border-left-color: @info-color;
|
||||
}
|
||||
|
||||
.om-callout-node-danger {
|
||||
background-color: @callout-danger-bg;
|
||||
border-left-color: @error-color;
|
||||
}
|
||||
|
||||
.callout-type-btn.ant-btn.ant-btn-text {
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.om-callout-node-popover {
|
||||
.ant-popover-inner-content {
|
||||
padding: 12px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.callout-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: baseline;
|
||||
.ant-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
gap: 4px;
|
||||
text-align: left;
|
||||
padding: 8px 16px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.om-callout-node-content {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@ -11,6 +11,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { EditorOptions } from '@tiptap/core';
|
||||
import { ReactComponent as IconDanger } from '../assets/svg/callout-danger.svg';
|
||||
import { ReactComponent as IconInfo } from '../assets/svg/callout-info.svg';
|
||||
import { ReactComponent as IconNote } from '../assets/svg/callout-note.svg';
|
||||
import { ReactComponent as IconWarning } from '../assets/svg/callout-warning.svg';
|
||||
|
||||
export const EDITOR_OPTIONS: Partial<EditorOptions> = {
|
||||
enableInputRules: [
|
||||
@ -26,6 +30,7 @@ export const EDITOR_OPTIONS: Partial<EditorOptions> = {
|
||||
'strike',
|
||||
'image',
|
||||
'taskItem',
|
||||
'callout',
|
||||
],
|
||||
parseOptions: {
|
||||
preserveWhitespace: 'full',
|
||||
@ -48,3 +53,13 @@ export const CLICKABLE_NODES = [
|
||||
];
|
||||
|
||||
export const DROP_CURSOR_COLOR = '#ebf6fe';
|
||||
|
||||
export const CALLOUT_CONTENT = {
|
||||
note: IconNote,
|
||||
warning: IconWarning,
|
||||
info: IconInfo,
|
||||
danger: IconDanger,
|
||||
};
|
||||
|
||||
export const CALL_OUT_REGEX = /^:::([A-Za-z]*)?$/;
|
||||
export const CALL_OUT_INPUT_RULE_REGEX = /^::: $/;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user