feat: markdown toolbar customization (#776)
This commit is contained in:
parent
a7ab1a7c0d
commit
cd13f3d193
@ -525,6 +525,42 @@ collections:
|
|||||||
widget: markdown
|
widget: markdown
|
||||||
media_library:
|
media_library:
|
||||||
folder_support: true
|
folder_support: true
|
||||||
|
- name: customized_buttons
|
||||||
|
label: Customized Buttons
|
||||||
|
widget: markdown
|
||||||
|
toolbar_buttons:
|
||||||
|
main:
|
||||||
|
- bold
|
||||||
|
- italic
|
||||||
|
- font
|
||||||
|
- shortcode
|
||||||
|
- label: Insert
|
||||||
|
groups:
|
||||||
|
- items: ['image', 'file-link']
|
||||||
|
- items: ['insert-table']
|
||||||
|
empty:
|
||||||
|
- bold
|
||||||
|
- italic
|
||||||
|
- font
|
||||||
|
- label: Insert
|
||||||
|
groups:
|
||||||
|
- items: ['image', 'file-link']
|
||||||
|
- items: ['blockquote', 'code-block']
|
||||||
|
selection:
|
||||||
|
- bold
|
||||||
|
- italic
|
||||||
|
- font
|
||||||
|
- file-link
|
||||||
|
table_empty:
|
||||||
|
- insert-row
|
||||||
|
- insert-column
|
||||||
|
- delete-row
|
||||||
|
- delete-column
|
||||||
|
- delete-table
|
||||||
|
table_selection:
|
||||||
|
- bold
|
||||||
|
- italic
|
||||||
|
- font
|
||||||
- name: number
|
- name: number
|
||||||
label: Number
|
label: Number
|
||||||
file: _widgets/number.json
|
file: _widgets/number.json
|
||||||
|
2
packages/core/src/__mocks__/@udecode/plate-list.ts
Normal file
2
packages/core/src/__mocks__/@udecode/plate-list.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const getListItemEntry = jest.fn();
|
||||||
|
export const toggleList = jest.fn();
|
20
packages/core/src/constants/toolbar_buttons.ts
Normal file
20
packages/core/src/constants/toolbar_buttons.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export const FONT_TOOLBAR_BUTTON = 'font';
|
||||||
|
export const SHORTCODE_TOOLBAR_BUTTON = 'shortcode';
|
||||||
|
export const BLOCKQUOTE_TOOLBAR_BUTTON = 'blockquote';
|
||||||
|
export const BOLD_TOOLBAR_BUTTON = 'bold';
|
||||||
|
export const CODE_BLOCK_TOOLBAR_BUTTON = 'code-block';
|
||||||
|
export const CODE_TOOLBAR_BUTTON = 'code';
|
||||||
|
export const DECREASE_IDENT_TOOLBAR_BUTTON = 'decrease-indent';
|
||||||
|
export const DELETE_COLUMN_TOOLBAR_BUTTON = 'delete-column';
|
||||||
|
export const DELETE_ROW_TOOLBAR_BUTTON = 'delete-row';
|
||||||
|
export const DELETE_TABLE_TOOLBAR_BUTTON = 'delete-table';
|
||||||
|
export const INCRASE_IDENT_TOOLBAR_BUTTON = 'increase-indent';
|
||||||
|
export const INSERT_COLUMN_TOOLBAR_BUTTON = 'insert-column';
|
||||||
|
export const IMAGE_TOOLBAR_BUTTON = 'image';
|
||||||
|
export const FILE_LINK_TOOLBAR_BUTTON = 'file-link';
|
||||||
|
export const INSERT_ROW_TOOLBAR_BUTTON = 'insert-row';
|
||||||
|
export const INSERT_TABLE_TOOLBAR_BUTTON = 'insert-table';
|
||||||
|
export const ITALIC_TOOLBAR_BUTTON = 'italic';
|
||||||
|
export const ORDERED_LIST_TOOLBAR_BUTTON = 'ordered-list';
|
||||||
|
export const STRIKETHROUGH_TOOLBAR_BUTTON = 'strikethrough';
|
||||||
|
export const UNORDERED_LIST_TOOLBAR_BUTTON = 'unordered-list';
|
@ -15,6 +15,28 @@ import type {
|
|||||||
SORT_DIRECTION_DESCENDING,
|
SORT_DIRECTION_DESCENDING,
|
||||||
SORT_DIRECTION_NONE,
|
SORT_DIRECTION_NONE,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import type {
|
||||||
|
BLOCKQUOTE_TOOLBAR_BUTTON,
|
||||||
|
BOLD_TOOLBAR_BUTTON,
|
||||||
|
CODE_BLOCK_TOOLBAR_BUTTON,
|
||||||
|
CODE_TOOLBAR_BUTTON,
|
||||||
|
DECREASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
DELETE_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
DELETE_ROW_TOOLBAR_BUTTON,
|
||||||
|
DELETE_TABLE_TOOLBAR_BUTTON,
|
||||||
|
FILE_LINK_TOOLBAR_BUTTON,
|
||||||
|
FONT_TOOLBAR_BUTTON,
|
||||||
|
IMAGE_TOOLBAR_BUTTON,
|
||||||
|
INCRASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
INSERT_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
INSERT_ROW_TOOLBAR_BUTTON,
|
||||||
|
INSERT_TABLE_TOOLBAR_BUTTON,
|
||||||
|
ITALIC_TOOLBAR_BUTTON,
|
||||||
|
ORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
SHORTCODE_TOOLBAR_BUTTON,
|
||||||
|
STRIKETHROUGH_TOOLBAR_BUTTON,
|
||||||
|
UNORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
} from './constants/toolbar_buttons';
|
||||||
import type { formatExtensions } from './formats/formats';
|
import type { formatExtensions } from './formats/formats';
|
||||||
import type {
|
import type {
|
||||||
I18N_FIELD_DUPLICATE,
|
I18N_FIELD_DUPLICATE,
|
||||||
@ -642,8 +664,52 @@ export interface MapField extends BaseField {
|
|||||||
height?: string;
|
height?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MarkdownToolbarButtonType =
|
||||||
|
| LowLevelMarkdownToolbarButtonType
|
||||||
|
| typeof FONT_TOOLBAR_BUTTON
|
||||||
|
| typeof SHORTCODE_TOOLBAR_BUTTON;
|
||||||
|
|
||||||
|
export type LowLevelMarkdownToolbarButtonType =
|
||||||
|
| typeof BLOCKQUOTE_TOOLBAR_BUTTON
|
||||||
|
| typeof BOLD_TOOLBAR_BUTTON
|
||||||
|
| typeof CODE_BLOCK_TOOLBAR_BUTTON
|
||||||
|
| typeof CODE_TOOLBAR_BUTTON
|
||||||
|
| typeof DECREASE_IDENT_TOOLBAR_BUTTON
|
||||||
|
| typeof DELETE_COLUMN_TOOLBAR_BUTTON
|
||||||
|
| typeof DELETE_ROW_TOOLBAR_BUTTON
|
||||||
|
| typeof DELETE_TABLE_TOOLBAR_BUTTON
|
||||||
|
| typeof INCRASE_IDENT_TOOLBAR_BUTTON
|
||||||
|
| typeof INSERT_COLUMN_TOOLBAR_BUTTON
|
||||||
|
| typeof IMAGE_TOOLBAR_BUTTON
|
||||||
|
| typeof FILE_LINK_TOOLBAR_BUTTON
|
||||||
|
| typeof INSERT_ROW_TOOLBAR_BUTTON
|
||||||
|
| typeof INSERT_TABLE_TOOLBAR_BUTTON
|
||||||
|
| typeof ITALIC_TOOLBAR_BUTTON
|
||||||
|
| typeof ORDERED_LIST_TOOLBAR_BUTTON
|
||||||
|
| typeof STRIKETHROUGH_TOOLBAR_BUTTON
|
||||||
|
| typeof UNORDERED_LIST_TOOLBAR_BUTTON;
|
||||||
|
|
||||||
|
export type MarkdownToolbarItem =
|
||||||
|
| MarkdownToolbarButtonType
|
||||||
|
| {
|
||||||
|
label: string;
|
||||||
|
icon?: string;
|
||||||
|
groups: {
|
||||||
|
items: LowLevelMarkdownToolbarButtonType[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface MarkdownFieldToolbarButtons {
|
||||||
|
main?: MarkdownToolbarItem[];
|
||||||
|
empty?: MarkdownToolbarItem[];
|
||||||
|
selection?: MarkdownToolbarItem[];
|
||||||
|
table_empty?: MarkdownToolbarItem[];
|
||||||
|
table_selection?: MarkdownToolbarItem[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface MarkdownField extends MediaField {
|
export interface MarkdownField extends MediaField {
|
||||||
widget: 'markdown';
|
widget: 'markdown';
|
||||||
|
toolbar_buttons?: MarkdownFieldToolbarButtons;
|
||||||
default?: string;
|
default?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,17 +17,73 @@ import { useFocused } from 'slate-react';
|
|||||||
|
|
||||||
import useDebounce from '@staticcms/core/lib/hooks/useDebounce';
|
import useDebounce from '@staticcms/core/lib/hooks/useDebounce';
|
||||||
import { isEmpty } from '@staticcms/core/lib/util/string.util';
|
import { isEmpty } from '@staticcms/core/lib/util/string.util';
|
||||||
|
import { selectVisible } from '@staticcms/core/reducers/selectors/mediaLibrary';
|
||||||
|
import { useAppSelector } from '@staticcms/core/store/hooks';
|
||||||
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
||||||
import BasicElementToolbarButtons from '../buttons/BasicElementToolbarButtons';
|
import { getToolbarButtons } from '../../hooks/useToolbarButtons';
|
||||||
import BasicMarkToolbarButtons from '../buttons/BasicMarkToolbarButtons';
|
import {
|
||||||
import MediaToolbarButtons from '../buttons/MediaToolbarButtons';
|
BOLD_TOOLBAR_BUTTON,
|
||||||
import ShortcodeToolbarButton from '../buttons/ShortcodeToolbarButton';
|
CODE_TOOLBAR_BUTTON,
|
||||||
import TableToolbarButtons from '../buttons/TableToolbarButtons';
|
DELETE_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
DELETE_ROW_TOOLBAR_BUTTON,
|
||||||
|
DELETE_TABLE_TOOLBAR_BUTTON,
|
||||||
|
FILE_LINK_TOOLBAR_BUTTON,
|
||||||
|
FONT_TOOLBAR_BUTTON,
|
||||||
|
IMAGE_TOOLBAR_BUTTON,
|
||||||
|
INSERT_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
INSERT_ROW_TOOLBAR_BUTTON,
|
||||||
|
ITALIC_TOOLBAR_BUTTON,
|
||||||
|
SHORTCODE_TOOLBAR_BUTTON,
|
||||||
|
STRIKETHROUGH_TOOLBAR_BUTTON,
|
||||||
|
} from '@staticcms/core/constants/toolbar_buttons';
|
||||||
|
|
||||||
import type { Collection, MarkdownField } from '@staticcms/core/interface';
|
import type {
|
||||||
|
Collection,
|
||||||
|
MarkdownField,
|
||||||
|
MarkdownToolbarButtonType,
|
||||||
|
} from '@staticcms/core/interface';
|
||||||
import type { ClientRectObject } from '@udecode/plate';
|
import type { ClientRectObject } from '@udecode/plate';
|
||||||
import type { FC, ReactNode } from 'react';
|
import type { FC, ReactNode } from 'react';
|
||||||
|
|
||||||
|
const DEFAULT_EMPTY_BUTTONS: MarkdownToolbarButtonType[] = [];
|
||||||
|
|
||||||
|
const DEFAULT_SELECTION_BUTTONS: MarkdownToolbarButtonType[] = [
|
||||||
|
BOLD_TOOLBAR_BUTTON,
|
||||||
|
ITALIC_TOOLBAR_BUTTON,
|
||||||
|
STRIKETHROUGH_TOOLBAR_BUTTON,
|
||||||
|
CODE_TOOLBAR_BUTTON,
|
||||||
|
FONT_TOOLBAR_BUTTON,
|
||||||
|
FILE_LINK_TOOLBAR_BUTTON,
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_TABLE_EMPTY_BUTTONS: MarkdownToolbarButtonType[] = [
|
||||||
|
BOLD_TOOLBAR_BUTTON,
|
||||||
|
ITALIC_TOOLBAR_BUTTON,
|
||||||
|
STRIKETHROUGH_TOOLBAR_BUTTON,
|
||||||
|
CODE_TOOLBAR_BUTTON,
|
||||||
|
INSERT_ROW_TOOLBAR_BUTTON,
|
||||||
|
DELETE_ROW_TOOLBAR_BUTTON,
|
||||||
|
INSERT_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
DELETE_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
DELETE_TABLE_TOOLBAR_BUTTON,
|
||||||
|
FILE_LINK_TOOLBAR_BUTTON,
|
||||||
|
IMAGE_TOOLBAR_BUTTON,
|
||||||
|
SHORTCODE_TOOLBAR_BUTTON,
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_TABLE_SELECTION_BUTTONS: MarkdownToolbarButtonType[] = [
|
||||||
|
BOLD_TOOLBAR_BUTTON,
|
||||||
|
ITALIC_TOOLBAR_BUTTON,
|
||||||
|
STRIKETHROUGH_TOOLBAR_BUTTON,
|
||||||
|
CODE_TOOLBAR_BUTTON,
|
||||||
|
INSERT_ROW_TOOLBAR_BUTTON,
|
||||||
|
DELETE_ROW_TOOLBAR_BUTTON,
|
||||||
|
INSERT_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
DELETE_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
DELETE_TABLE_TOOLBAR_BUTTON,
|
||||||
|
FILE_LINK_TOOLBAR_BUTTON,
|
||||||
|
];
|
||||||
|
|
||||||
export interface BalloonToolbarProps {
|
export interface BalloonToolbarProps {
|
||||||
useMdx: boolean;
|
useMdx: boolean;
|
||||||
containerRef: HTMLElement | null;
|
containerRef: HTMLElement | null;
|
||||||
@ -49,6 +105,8 @@ const BalloonToolbar: FC<BalloonToolbarProps> = ({
|
|||||||
const [hasFocus, setHasFocus] = useState(false);
|
const [hasFocus, setHasFocus] = useState(false);
|
||||||
const debouncedHasFocus = useDebounce(hasFocus, 150);
|
const debouncedHasFocus = useDebounce(hasFocus, 150);
|
||||||
|
|
||||||
|
const isMediaLibraryOpen = useAppSelector(selectVisible);
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
const handleFocus = useCallback(() => {
|
||||||
setHasFocus(true);
|
setHasFocus(true);
|
||||||
}, []);
|
}, []);
|
||||||
@ -91,79 +149,57 @@ const BalloonToolbar: FC<BalloonToolbarProps> = ({
|
|||||||
|
|
||||||
const debouncedEditorFocus = useDebounce(hasEditorFocus, 150);
|
const debouncedEditorFocus = useDebounce(hasEditorFocus, 150);
|
||||||
|
|
||||||
const groups: ReactNode[] = useMemo(() => {
|
const [groups, setGroups] = useState<ReactNode[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMediaLibraryOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!debouncedEditorFocus && !hasFocus && !debouncedHasFocus) {
|
if (!debouncedEditorFocus && !hasFocus && !debouncedHasFocus) {
|
||||||
return [];
|
setGroups([]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection && someNode(editor, { match: { type: ELEMENT_LINK }, at: selection?.anchor })) {
|
if (selection && someNode(editor, { match: { type: ELEMENT_LINK }, at: selection?.anchor })) {
|
||||||
return [];
|
setGroups([]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selected text buttons
|
// Selected text buttons
|
||||||
if (selectionText && selectionExpanded) {
|
if (selectionText && selectionExpanded) {
|
||||||
return [
|
setGroups(
|
||||||
<BasicMarkToolbarButtons
|
getToolbarButtons(
|
||||||
key="selection-basic-mark-buttons"
|
isInTableCell
|
||||||
useMdx={useMdx}
|
? field.toolbar_buttons?.table_selection ?? DEFAULT_TABLE_SELECTION_BUTTONS
|
||||||
disabled={disabled}
|
: field.toolbar_buttons?.selection ?? DEFAULT_SELECTION_BUTTONS,
|
||||||
/>,
|
collection,
|
||||||
<BasicElementToolbarButtons
|
field,
|
||||||
key="selection-basic-element-buttons"
|
disabled,
|
||||||
hideFontTypeSelect={isInTableCell}
|
|
||||||
hideCodeBlock
|
|
||||||
disabled={disabled}
|
|
||||||
/>,
|
|
||||||
isInTableCell && (
|
|
||||||
<TableToolbarButtons key="selection-table-toolbar-buttons" disabled={disabled} />
|
|
||||||
),
|
),
|
||||||
<MediaToolbarButtons
|
);
|
||||||
key="selection-media-buttons"
|
return;
|
||||||
collection={collection}
|
|
||||||
field={field}
|
|
||||||
hideImages
|
|
||||||
disabled={disabled}
|
|
||||||
/>,
|
|
||||||
].filter(Boolean);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const allButtons = [
|
|
||||||
<BasicMarkToolbarButtons
|
|
||||||
key="empty-basic-mark-buttons"
|
|
||||||
useMdx={useMdx}
|
|
||||||
disabled={disabled}
|
|
||||||
/>,
|
|
||||||
<BasicElementToolbarButtons
|
|
||||||
key="empty-basic-element-buttons"
|
|
||||||
hideFontTypeSelect={isInTableCell}
|
|
||||||
hideCodeBlock
|
|
||||||
disabled={disabled}
|
|
||||||
/>,
|
|
||||||
<TableToolbarButtons
|
|
||||||
key="empty-table-toolbar-buttons"
|
|
||||||
isInTable={isInTableCell}
|
|
||||||
disabled={disabled}
|
|
||||||
/>,
|
|
||||||
<MediaToolbarButtons
|
|
||||||
key="empty-media-buttons"
|
|
||||||
collection={collection}
|
|
||||||
field={field}
|
|
||||||
disabled={disabled}
|
|
||||||
/>,
|
|
||||||
!useMdx ? <ShortcodeToolbarButton key="shortcode-button" disabled={disabled} /> : null,
|
|
||||||
].filter(Boolean);
|
|
||||||
|
|
||||||
// Empty table cell
|
// Empty table cell
|
||||||
if (
|
if (
|
||||||
isInTableCell &&
|
|
||||||
editor.children.length > 1 &&
|
|
||||||
node &&
|
node &&
|
||||||
((isElement(node) && isElementEmpty(editor, node)) || (isText(node) && isEmpty(node.text)))
|
((isElement(node) && isElementEmpty(editor, node)) || (isText(node) && isEmpty(node.text)))
|
||||||
) {
|
) {
|
||||||
return allButtons;
|
setGroups(
|
||||||
|
getToolbarButtons(
|
||||||
|
isInTableCell
|
||||||
|
? field.toolbar_buttons?.table_empty ?? DEFAULT_TABLE_EMPTY_BUTTONS
|
||||||
|
: field.toolbar_buttons?.empty ?? DEFAULT_EMPTY_BUTTONS,
|
||||||
|
collection,
|
||||||
|
field,
|
||||||
|
disabled,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
setGroups([]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
debouncedEditorFocus,
|
debouncedEditorFocus,
|
||||||
@ -179,6 +215,7 @@ const BalloonToolbar: FC<BalloonToolbarProps> = ({
|
|||||||
containerRef,
|
containerRef,
|
||||||
collection,
|
collection,
|
||||||
field,
|
field,
|
||||||
|
isMediaLibraryOpen,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [prevSelectionBoundingClientRect, setPrevSelectionBoundingClientRect] = useState(
|
const [prevSelectionBoundingClientRect, setPrevSelectionBoundingClientRect] = useState(
|
||||||
@ -190,8 +227,8 @@ const BalloonToolbar: FC<BalloonToolbarProps> = ({
|
|||||||
prevSelectionBoundingClientRect !== selectionBoundingClientRect ? 0 : 150,
|
prevSelectionBoundingClientRect !== selectionBoundingClientRect ? 0 : 150,
|
||||||
);
|
);
|
||||||
const open = useMemo(
|
const open = useMemo(
|
||||||
() => groups.length > 0 || debouncedGroups.length > 0,
|
() => groups.length > 0 || debouncedGroups.length > 0 || isMediaLibraryOpen,
|
||||||
[debouncedGroups.length, groups.length],
|
[debouncedGroups.length, groups.length, isMediaLibraryOpen],
|
||||||
);
|
);
|
||||||
const debouncedOpen = useDebounce(
|
const debouncedOpen = useDebounce(
|
||||||
open,
|
open,
|
||||||
@ -239,10 +276,11 @@ const BalloonToolbar: FC<BalloonToolbarProps> = ({
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
data-testid="balloon-toolbar"
|
||||||
className="
|
className="
|
||||||
flex
|
flex
|
||||||
gap-0.5
|
gap-0.5
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{groups.length > 0 ? groups : debouncedGroups}
|
{groups.length > 0 ? groups : debouncedGroups}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,14 +2,16 @@
|
|||||||
* @jest-environment jsdom
|
* @jest-environment jsdom
|
||||||
*/
|
*/
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import { screen } from '@testing-library/react';
|
import { act, screen } from '@testing-library/react';
|
||||||
import {
|
import {
|
||||||
ELEMENT_LINK,
|
ELEMENT_LINK,
|
||||||
findNodePath,
|
findNodePath,
|
||||||
getNode,
|
getNode,
|
||||||
getParentNode,
|
getParentNode,
|
||||||
|
getSelectionText,
|
||||||
isElement,
|
isElement,
|
||||||
isElementEmpty,
|
isElementEmpty,
|
||||||
|
isSelectionExpanded,
|
||||||
someNode,
|
someNode,
|
||||||
usePlateEditorState,
|
usePlateEditorState,
|
||||||
usePlateSelection,
|
usePlateSelection,
|
||||||
@ -67,8 +69,12 @@ describe(BalloonToolbar.name, () => {
|
|||||||
const mockUseFocused = useFocused as jest.Mock;
|
const mockUseFocused = useFocused as jest.Mock;
|
||||||
const mockFindNodePath = findNodePath as jest.Mock;
|
const mockFindNodePath = findNodePath as jest.Mock;
|
||||||
const mockGetParentNode = getParentNode as jest.Mock;
|
const mockGetParentNode = getParentNode as jest.Mock;
|
||||||
|
const mockGetSelectionText = getSelectionText as jest.Mock;
|
||||||
|
const mockIsSelectionExpanded = isSelectionExpanded as jest.Mock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
store.dispatch(configLoaded(config as unknown as Config));
|
store.dispatch(configLoaded(config as unknown as Config));
|
||||||
|
|
||||||
mockEditor = {
|
mockEditor = {
|
||||||
@ -78,107 +84,158 @@ describe(BalloonToolbar.name, () => {
|
|||||||
mockUseEditor.mockReturnValue(mockEditor);
|
mockUseEditor.mockReturnValue(mockEditor);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders empty div by default', () => {
|
it('renders empty div by default', () => {
|
||||||
renderWithProviders(<BalloonToolbarWrapper />);
|
renderWithProviders(<BalloonToolbarWrapper />);
|
||||||
expect(screen.queryAllByRole('button').length).toBe(0);
|
expect(screen.queryAllByRole('button').length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('empty node toolbar inside table', () => {
|
interface BalloonToolbarSetupOptions {
|
||||||
interface EmptyNodeToolbarSetupOptions {
|
inTable?: boolean;
|
||||||
useMdx?: boolean;
|
selectedText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyNodeToolbarSetup = ({ useMdx }: EmptyNodeToolbarSetupOptions = {}) => {
|
const emptyNodeToolbarSetup = ({ inTable, selectedText }: BalloonToolbarSetupOptions = {}) => {
|
||||||
mockEditor = {
|
mockEditor = {
|
||||||
selection: {
|
selection: {
|
||||||
anchor: {
|
anchor: {
|
||||||
path: [1, 0],
|
path: [1, 0],
|
||||||
offset: 0,
|
offset: 0,
|
||||||
},
|
},
|
||||||
focus: {
|
focus: {
|
||||||
path: [1, 0],
|
path: [1, 0],
|
||||||
offset: 0,
|
offset: 0,
|
||||||
},
|
},
|
||||||
} as TRange,
|
} as TRange,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
type: 'p',
|
type: 'p',
|
||||||
children: [{ text: '' }],
|
children: [{ text: !inTable && selectedText ? selectedText : '' }],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'td',
|
|
||||||
children: [{ text: '' }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as unknown as MdEditor;
|
|
||||||
|
|
||||||
mockUseEditor.mockReturnValue(mockEditor);
|
|
||||||
mockUsePlateSelection.mockReturnValue(mockEditor.selection);
|
|
||||||
|
|
||||||
mockGetNode.mockReturnValue({ text: '' });
|
|
||||||
mockIsElement.mockReturnValue(true);
|
|
||||||
mockIsElementEmpty.mockReturnValue(true);
|
|
||||||
mockSomeNode.mockImplementation((_editor, { match: { type } }) => type !== ELEMENT_LINK);
|
|
||||||
mockUseFocused.mockReturnValue(true);
|
|
||||||
|
|
||||||
mockFindNodePath.mockReturnValue([1, 0]);
|
|
||||||
mockGetParentNode.mockReturnValue([
|
|
||||||
{
|
{
|
||||||
type: 'td',
|
type: 'td',
|
||||||
children: [{ text: '' }],
|
children: [{ text: inTable && selectedText ? selectedText : '' }],
|
||||||
},
|
},
|
||||||
]);
|
],
|
||||||
|
} as unknown as MdEditor;
|
||||||
|
|
||||||
const result = renderWithProviders(<BalloonToolbarWrapper />);
|
mockUseEditor.mockReturnValue(mockEditor);
|
||||||
|
mockUsePlateSelection.mockReturnValue(mockEditor.selection);
|
||||||
|
|
||||||
result.rerender(<BalloonToolbarWrapper useMdx={useMdx} />);
|
mockGetNode.mockReturnValue({ text: '' });
|
||||||
|
mockIsElement.mockReturnValue(true);
|
||||||
|
mockIsElementEmpty.mockReturnValue(true);
|
||||||
|
mockUseFocused.mockReturnValue(true);
|
||||||
|
|
||||||
return result;
|
if (selectedText) {
|
||||||
};
|
mockIsSelectionExpanded.mockReturnValue(true);
|
||||||
|
mockGetSelectionText.mockReturnValue(selectedText);
|
||||||
|
} else {
|
||||||
|
mockIsSelectionExpanded.mockReturnValue(false);
|
||||||
|
mockGetSelectionText.mockReturnValue('');
|
||||||
|
}
|
||||||
|
|
||||||
it('renders empty node toolbar for markdown', () => {
|
if (inTable) {
|
||||||
emptyNodeToolbarSetup();
|
mockSomeNode.mockImplementation((_editor, { match: { type } }) => type !== ELEMENT_LINK);
|
||||||
|
mockFindNodePath.mockReturnValue([1, 0]);
|
||||||
|
} else {
|
||||||
|
mockSomeNode.mockReturnValue(false);
|
||||||
|
mockFindNodePath.mockReturnValue([0, 0]);
|
||||||
|
}
|
||||||
|
|
||||||
expect(screen.queryByTestId('toolbar-button-bold')).toBeInTheDocument();
|
mockGetParentNode.mockReturnValue([
|
||||||
expect(screen.queryByTestId('toolbar-button-italic')).toBeInTheDocument();
|
{
|
||||||
expect(screen.queryByTestId('toolbar-button-code')).toBeInTheDocument();
|
type: 'td',
|
||||||
expect(screen.queryByTestId('toolbar-button-strikethrough')).toBeInTheDocument();
|
children: [{ text: '' }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
expect(screen.queryByTestId('font-type-select')).not.toBeInTheDocument();
|
const result = renderWithProviders(<BalloonToolbarWrapper />);
|
||||||
|
|
||||||
expect(screen.queryByTestId('toolbar-button-insert-row')).toBeInTheDocument();
|
act(() => {
|
||||||
expect(screen.queryByTestId('toolbar-button-delete-row')).toBeInTheDocument();
|
jest.advanceTimersByTime(1000);
|
||||||
expect(screen.queryByTestId('toolbar-button-insert-column')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByTestId('toolbar-button-delete-column')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByTestId('toolbar-button-delete-table')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(screen.queryByTestId('toolbar-button-insert-link')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByTestId('toolbar-button-insert-image')).toBeInTheDocument();
|
|
||||||
|
|
||||||
// MDX Only do not show for markdown version
|
|
||||||
expect(screen.queryByTestId('toolbar-button-underline')).not.toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders empty node toolbar for mdx', () => {
|
result.rerender(<BalloonToolbarWrapper />);
|
||||||
emptyNodeToolbarSetup({ useMdx: true });
|
|
||||||
|
|
||||||
expect(screen.queryByTestId('toolbar-button-bold')).toBeInTheDocument();
|
return result;
|
||||||
expect(screen.queryByTestId('toolbar-button-italic')).toBeInTheDocument();
|
};
|
||||||
expect(screen.queryByTestId('toolbar-button-code')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByTestId('toolbar-button-strikethrough')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(screen.queryByTestId('font-type-select')).not.toBeInTheDocument();
|
it('does not render empty node toolbar', () => {
|
||||||
|
emptyNodeToolbarSetup();
|
||||||
|
|
||||||
expect(screen.queryByTestId('toolbar-button-insert-row')).toBeInTheDocument();
|
expect(screen.queryByTestId('balloon-toolbar')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId('toolbar-button-delete-row')).toBeInTheDocument();
|
});
|
||||||
expect(screen.queryByTestId('toolbar-button-insert-column')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByTestId('toolbar-button-delete-column')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByTestId('toolbar-button-delete-table')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(screen.queryByTestId('toolbar-button-insert-link')).toBeInTheDocument();
|
it('renders selected node toolbar when text is selected', () => {
|
||||||
expect(screen.queryByTestId('toolbar-button-insert-image')).toBeInTheDocument();
|
emptyNodeToolbarSetup({ selectedText: 'Test Text' });
|
||||||
|
|
||||||
expect(screen.queryByTestId('toolbar-button-underline')).toBeInTheDocument();
|
expect(screen.queryByTestId('balloon-toolbar')).toBeInTheDocument();
|
||||||
});
|
|
||||||
|
expect(screen.queryByTestId('toolbar-button-bold')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-italic')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-code')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-strikethrough')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('font-type-select')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('toolbar-button-insert-row')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-delete-row')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-insert-column')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-delete-column')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-delete-table')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('toolbar-button-link')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-image')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-shortcode')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders empty table node toolbar when in table', () => {
|
||||||
|
emptyNodeToolbarSetup({ inTable: true });
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('balloon-toolbar')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('toolbar-button-bold')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-italic')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-code')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-strikethrough')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('font-type-select')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('toolbar-button-insert-row')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-delete-row')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-insert-column')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-delete-column')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-delete-table')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('toolbar-button-link')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-image')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-shortcode')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders selected table node toolbar when text is selected in table', () => {
|
||||||
|
emptyNodeToolbarSetup({ inTable: true, selectedText: 'Test Text' });
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('balloon-toolbar')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('toolbar-button-bold')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-italic')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-code')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-strikethrough')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('font-type-select')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('toolbar-button-insert-row')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-delete-row')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-insert-column')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-delete-column')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-delete-table')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('toolbar-button-link')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-image')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('toolbar-button-shortcode')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
import { TableAdd } from '@styled-icons/fluentui-system-regular/TableAdd';
|
|
||||||
import { Add as AddIcon } from '@styled-icons/material/Add';
|
|
||||||
import { Code as CodeIcon } from '@styled-icons/material/Code';
|
|
||||||
import { FormatQuote as FormatQuoteIcon } from '@styled-icons/material/FormatQuote';
|
|
||||||
import {
|
|
||||||
ELEMENT_BLOCKQUOTE,
|
|
||||||
ELEMENT_CODE_BLOCK,
|
|
||||||
ELEMENT_IMAGE,
|
|
||||||
ELEMENT_LINK,
|
|
||||||
ELEMENT_TABLE,
|
|
||||||
insertEmptyCodeBlock,
|
|
||||||
insertTable,
|
|
||||||
toggleNodeType,
|
|
||||||
} from '@udecode/plate';
|
|
||||||
import React, { useCallback } from 'react';
|
|
||||||
|
|
||||||
import Menu from '@staticcms/core/components/common/menu/Menu';
|
|
||||||
import MenuGroup from '@staticcms/core/components/common/menu/MenuGroup';
|
|
||||||
import MenuItemButton from '@staticcms/core/components/common/menu/MenuItemButton';
|
|
||||||
import { useMdPlateEditorState } from '../../plateTypes';
|
|
||||||
import ImageToolbarButton from './common/ImageToolbarButton';
|
|
||||||
import LinkToolbarButton from './common/LinkToolbarButton';
|
|
||||||
|
|
||||||
import type { Collection, MarkdownField } from '@staticcms/core/interface';
|
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
interface AddButtonsProps {
|
|
||||||
collection: Collection<MarkdownField>;
|
|
||||||
field: MarkdownField;
|
|
||||||
disabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AddButtons: FC<AddButtonsProps> = ({ collection, field, disabled }) => {
|
|
||||||
const editor = useMdPlateEditorState();
|
|
||||||
|
|
||||||
const handleBlockOnClick = useCallback(
|
|
||||||
(type: string, inactiveType?: string) => () => {
|
|
||||||
toggleNodeType(editor, { activeType: type, inactiveType });
|
|
||||||
},
|
|
||||||
[editor],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCodeBlockOnClick = useCallback(() => {
|
|
||||||
insertEmptyCodeBlock(editor, {
|
|
||||||
insertNodesOptions: { select: true },
|
|
||||||
});
|
|
||||||
}, [editor]);
|
|
||||||
|
|
||||||
const handleTableAdd = useCallback(() => {
|
|
||||||
insertTable(editor, {
|
|
||||||
rowCount: 2,
|
|
||||||
colCount: 2,
|
|
||||||
});
|
|
||||||
}, [editor]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu
|
|
||||||
label={<AddIcon className="h-5 w-5" aria-hidden="true" />}
|
|
||||||
data-testid="toolbar-add-buttons"
|
|
||||||
keepMounted
|
|
||||||
hideDropdownIcon
|
|
||||||
variant="text"
|
|
||||||
className="
|
|
||||||
py-0.5
|
|
||||||
px-0.5
|
|
||||||
h-7
|
|
||||||
w-7
|
|
||||||
"
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
<MenuGroup>
|
|
||||||
<MenuItemButton
|
|
||||||
key={ELEMENT_BLOCKQUOTE}
|
|
||||||
onClick={handleBlockOnClick(ELEMENT_BLOCKQUOTE)}
|
|
||||||
startIcon={FormatQuoteIcon}
|
|
||||||
>
|
|
||||||
Blockquote
|
|
||||||
</MenuItemButton>
|
|
||||||
<MenuItemButton
|
|
||||||
key={ELEMENT_CODE_BLOCK}
|
|
||||||
onClick={handleCodeBlockOnClick}
|
|
||||||
startIcon={CodeIcon}
|
|
||||||
>
|
|
||||||
Code Block
|
|
||||||
</MenuItemButton>
|
|
||||||
</MenuGroup>
|
|
||||||
<MenuGroup>
|
|
||||||
<MenuItemButton key={ELEMENT_TABLE} onClick={handleTableAdd} startIcon={TableAdd}>
|
|
||||||
Table
|
|
||||||
</MenuItemButton>
|
|
||||||
</MenuGroup>
|
|
||||||
<MenuGroup>
|
|
||||||
<ImageToolbarButton
|
|
||||||
key={ELEMENT_IMAGE}
|
|
||||||
collection={collection}
|
|
||||||
field={field}
|
|
||||||
variant="menu"
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<LinkToolbarButton
|
|
||||||
key={ELEMENT_LINK}
|
|
||||||
collection={collection}
|
|
||||||
field={field}
|
|
||||||
variant="menu"
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</MenuGroup>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddButtons;
|
|
@ -1,42 +0,0 @@
|
|||||||
import { FormatAlignCenter as FormatAlignCenterIcon } from '@styled-icons/material/FormatAlignCenter';
|
|
||||||
import { FormatAlignLeft as FormatAlignLeftIcon } from '@styled-icons/material/FormatAlignLeft';
|
|
||||||
import { FormatAlignRight as FormatAlignRightIcon } from '@styled-icons/material/FormatAlignRight';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import AlignToolbarButton from './common/AlignToolbarButton';
|
|
||||||
|
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
interface AlignToolbarButtonsProps {
|
|
||||||
disabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AlignToolbarButtons: FC<AlignToolbarButtonsProps> = ({ disabled }) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<AlignToolbarButton
|
|
||||||
key="algin-button-left"
|
|
||||||
tooltip="Align Left"
|
|
||||||
value="left"
|
|
||||||
icon={<FormatAlignLeftIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<AlignToolbarButton
|
|
||||||
key="algin-button-center"
|
|
||||||
tooltip="Align Center"
|
|
||||||
value="center"
|
|
||||||
icon={<FormatAlignCenterIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<AlignToolbarButton
|
|
||||||
key="algin-button-right"
|
|
||||||
tooltip="Align Right"
|
|
||||||
value="right"
|
|
||||||
icon={<FormatAlignRightIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AlignToolbarButtons;
|
|
@ -1,24 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import FontTypeSelect from './FontTypeSelect';
|
|
||||||
|
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
export interface BasicElementToolbarButtonsProps {
|
|
||||||
hideFontTypeSelect?: boolean;
|
|
||||||
disableFontTypeSelect?: boolean;
|
|
||||||
hideCodeBlock?: boolean;
|
|
||||||
disabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BasicElementToolbarButtons: FC<BasicElementToolbarButtonsProps> = ({
|
|
||||||
hideFontTypeSelect = false,
|
|
||||||
disableFontTypeSelect = false,
|
|
||||||
disabled,
|
|
||||||
}) => {
|
|
||||||
return !hideFontTypeSelect ? (
|
|
||||||
<FontTypeSelect disabled={disableFontTypeSelect || disabled} />
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BasicElementToolbarButtons;
|
|
@ -1,93 +0,0 @@
|
|||||||
import { Code as CodeIcon } from '@styled-icons/material/Code';
|
|
||||||
import { FormatBold as FormatBoldIcon } from '@styled-icons/material/FormatBold';
|
|
||||||
import { FormatItalic as FormatItalicIcon } from '@styled-icons/material/FormatItalic';
|
|
||||||
import { FormatStrikethrough as FormatStrikethroughIcon } from '@styled-icons/material/FormatStrikethrough';
|
|
||||||
import { FormatUnderlined as FormatUnderlinedIcon } from '@styled-icons/material/FormatUnderlined';
|
|
||||||
import { Subscript as SubscriptIcon } from '@styled-icons/material/Subscript';
|
|
||||||
import { Superscript as SuperscriptIcon } from '@styled-icons/material/Superscript';
|
|
||||||
import {
|
|
||||||
MARK_BOLD,
|
|
||||||
MARK_CODE,
|
|
||||||
MARK_ITALIC,
|
|
||||||
MARK_STRIKETHROUGH,
|
|
||||||
MARK_SUBSCRIPT,
|
|
||||||
MARK_SUPERSCRIPT,
|
|
||||||
MARK_UNDERLINE,
|
|
||||||
} from '@udecode/plate';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import MarkToolbarButton from './common/MarkToolbarButton';
|
|
||||||
|
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
export interface BasicMarkToolbarButtonsProps {
|
|
||||||
extended?: boolean;
|
|
||||||
useMdx: boolean;
|
|
||||||
disabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BasicMarkToolbarButtons: FC<BasicMarkToolbarButtonsProps> = ({
|
|
||||||
extended = false,
|
|
||||||
useMdx,
|
|
||||||
disabled,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MarkToolbarButton
|
|
||||||
tooltip="Bold"
|
|
||||||
type={MARK_BOLD}
|
|
||||||
icon={<FormatBoldIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<MarkToolbarButton
|
|
||||||
tooltip="Italic"
|
|
||||||
type={MARK_ITALIC}
|
|
||||||
icon={<FormatItalicIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
{useMdx ? (
|
|
||||||
<MarkToolbarButton
|
|
||||||
key="underline-button"
|
|
||||||
tooltip="Underline"
|
|
||||||
type={MARK_UNDERLINE}
|
|
||||||
icon={<FormatUnderlinedIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<MarkToolbarButton
|
|
||||||
tooltip="Strikethrough"
|
|
||||||
type={MARK_STRIKETHROUGH}
|
|
||||||
icon={<FormatStrikethroughIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<MarkToolbarButton
|
|
||||||
tooltip="Code"
|
|
||||||
type={MARK_CODE}
|
|
||||||
icon={<CodeIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
{useMdx && extended ? (
|
|
||||||
<>
|
|
||||||
<MarkToolbarButton
|
|
||||||
key="superscript-button"
|
|
||||||
tooltip="Superscript"
|
|
||||||
type={MARK_SUPERSCRIPT}
|
|
||||||
clear={MARK_SUBSCRIPT}
|
|
||||||
icon={<SuperscriptIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<MarkToolbarButton
|
|
||||||
key="subscript-button"
|
|
||||||
tooltip="Subscript"
|
|
||||||
type={MARK_SUBSCRIPT}
|
|
||||||
clear={MARK_SUPERSCRIPT}
|
|
||||||
icon={<SubscriptIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BasicMarkToolbarButtons;
|
|
@ -0,0 +1,27 @@
|
|||||||
|
import { FormatQuote as FormatQuoteIcon } from '@styled-icons/material/FormatQuote';
|
||||||
|
import { ELEMENT_BLOCKQUOTE } from '@udecode/plate';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import BlockToolbarButton from './common/BlockToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface BlockquoteToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const BlockquoteToolbarButton: FC<BlockquoteToolbarButtonProps> = ({ disabled, variant }) => {
|
||||||
|
return (
|
||||||
|
<BlockToolbarButton
|
||||||
|
label="Blockquote"
|
||||||
|
tooltip="Insert blockquote"
|
||||||
|
icon={FormatQuoteIcon}
|
||||||
|
type={ELEMENT_BLOCKQUOTE}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BlockquoteToolbarButton;
|
@ -0,0 +1,26 @@
|
|||||||
|
import { FormatBold as FormatBoldIcon } from '@styled-icons/material/FormatBold';
|
||||||
|
import { MARK_BOLD } from '@udecode/plate';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import MarkToolbarButton from './common/MarkToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface BoldToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const BoldToolbarButton: FC<BoldToolbarButtonProps> = ({ disabled, variant }) => {
|
||||||
|
return (
|
||||||
|
<MarkToolbarButton
|
||||||
|
tooltip="Bold"
|
||||||
|
type={MARK_BOLD}
|
||||||
|
variant={variant}
|
||||||
|
icon={FormatBoldIcon}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BoldToolbarButton;
|
@ -0,0 +1,36 @@
|
|||||||
|
import { Code as CodeIcon } from '@styled-icons/material/Code';
|
||||||
|
import { insertEmptyCodeBlock } from '@udecode/plate';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useMdPlateEditorState } from '../../plateTypes';
|
||||||
|
import ToolbarButton from './common/ToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface CodeBlockToolbarButtonsProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const CodeBlockToolbarButtons: FC<CodeBlockToolbarButtonsProps> = ({ disabled, variant }) => {
|
||||||
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
|
const handleCodeBlockOnClick = useCallback(() => {
|
||||||
|
insertEmptyCodeBlock(editor, {
|
||||||
|
insertNodesOptions: { select: true },
|
||||||
|
});
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
label="Code block"
|
||||||
|
tooltip="Insert code block"
|
||||||
|
icon={CodeIcon}
|
||||||
|
onClick={handleCodeBlockOnClick}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CodeBlockToolbarButtons;
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Code as CodeIcon } from '@styled-icons/material/Code';
|
||||||
|
import { MARK_CODE } from '@udecode/plate';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import MarkToolbarButton from './common/MarkToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface CodeToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const CodeToolbarButton: FC<CodeToolbarButtonProps> = ({ disabled, variant }) => {
|
||||||
|
return (
|
||||||
|
<MarkToolbarButton
|
||||||
|
tooltip="Code"
|
||||||
|
type={MARK_CODE}
|
||||||
|
icon={CodeIcon}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CodeToolbarButton;
|
@ -1,35 +0,0 @@
|
|||||||
import { FontDownload as FontDownloadIcon } from '@styled-icons/material/FontDownload';
|
|
||||||
import { FormatColorText as FormatColorTextIcon } from '@styled-icons/material/FormatColorText';
|
|
||||||
import { MARK_BG_COLOR, MARK_COLOR } from '@udecode/plate';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import ColorPickerToolbarDropdown from './common/ColorPickerToolbarDropdown';
|
|
||||||
|
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
interface ColorToolbarButtonsProps {
|
|
||||||
disabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ColorToolbarButtons: FC<ColorToolbarButtonsProps> = ({ disabled }) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ColorPickerToolbarDropdown
|
|
||||||
key="color-picker-button"
|
|
||||||
pluginKey={MARK_COLOR}
|
|
||||||
icon={<FormatColorTextIcon className="h-5 w-5" />}
|
|
||||||
tooltip="Color"
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<ColorPickerToolbarDropdown
|
|
||||||
key="background-color-picker-button"
|
|
||||||
pluginKey={MARK_BG_COLOR}
|
|
||||||
icon={<FontDownloadIcon className="h-5 w-5" />}
|
|
||||||
tooltip="Background Color"
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ColorToolbarButtons;
|
|
@ -0,0 +1,36 @@
|
|||||||
|
import { FormatIndentDecrease as FormatIndentDecreaseIcon } from '@styled-icons/material/FormatIndentDecrease';
|
||||||
|
import { outdent } from '@udecode/plate';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useMdPlateEditorState } from '../../plateTypes';
|
||||||
|
import ToolbarButton from './common/ToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface DecreaseIndentToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const DecreaseIndentToolbarButton: FC<DecreaseIndentToolbarButtonProps> = ({
|
||||||
|
disabled,
|
||||||
|
variant,
|
||||||
|
}) => {
|
||||||
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
|
const handleOutdent = useCallback(() => {
|
||||||
|
outdent(editor);
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
tooltip="Decrease indent"
|
||||||
|
onClick={handleOutdent}
|
||||||
|
icon={FormatIndentDecreaseIcon}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DecreaseIndentToolbarButton;
|
@ -0,0 +1,33 @@
|
|||||||
|
import { TableDeleteColumn } from '@styled-icons/fluentui-system-regular/TableDeleteColumn';
|
||||||
|
import { deleteColumn } from '@udecode/plate';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useMdPlateEditorState } from '../../plateTypes';
|
||||||
|
import ToolbarButton from './common/ToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface DeleteColumnToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteColumnToolbarButton: FC<DeleteColumnToolbarButtonProps> = ({ disabled, variant }) => {
|
||||||
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
|
const handleDeleteColumn = useCallback(() => {
|
||||||
|
deleteColumn(editor);
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
tooltip="Delete column"
|
||||||
|
icon={TableDeleteColumn}
|
||||||
|
onClick={handleDeleteColumn}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteColumnToolbarButton;
|
@ -0,0 +1,33 @@
|
|||||||
|
import { TableDeleteRow } from '@styled-icons/fluentui-system-regular/TableDeleteRow';
|
||||||
|
import { deleteRow } from '@udecode/plate';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useMdPlateEditorState } from '../../plateTypes';
|
||||||
|
import ToolbarButton from './common/ToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface DeleteRowToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteRowToolbarButton: FC<DeleteRowToolbarButtonProps> = ({ disabled, variant }) => {
|
||||||
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
|
const handleDeleteRow = useCallback(() => {
|
||||||
|
deleteRow(editor);
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
tooltip="Delete row"
|
||||||
|
icon={TableDeleteRow}
|
||||||
|
onClick={handleDeleteRow}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteRowToolbarButton;
|
@ -0,0 +1,33 @@
|
|||||||
|
import { TableDismiss } from '@styled-icons/fluentui-system-regular/TableDismiss';
|
||||||
|
import { deleteTable } from '@udecode/plate';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useMdPlateEditorState } from '../../plateTypes';
|
||||||
|
import ToolbarButton from './common/ToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface DeleteTableToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteTableToolbarButton: FC<DeleteTableToolbarButtonProps> = ({ disabled, variant }) => {
|
||||||
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
|
const handleDeleteTable = useCallback(() => {
|
||||||
|
deleteTable(editor);
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
tooltip="Delete table"
|
||||||
|
icon={TableDismiss}
|
||||||
|
onClick={handleDeleteTable}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteTableToolbarButton;
|
@ -0,0 +1,36 @@
|
|||||||
|
import { FormatIndentIncrease as FormatIndentIncreaseIcon } from '@styled-icons/material/FormatIndentIncrease';
|
||||||
|
import { indent } from '@udecode/plate';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useMdPlateEditorState } from '../../plateTypes';
|
||||||
|
import ToolbarButton from './common/ToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface IncreaseIndentToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const IncreaseIndentToolbarButton: FC<IncreaseIndentToolbarButtonProps> = ({
|
||||||
|
disabled,
|
||||||
|
variant,
|
||||||
|
}) => {
|
||||||
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
|
const handleIndent = useCallback(() => {
|
||||||
|
indent(editor);
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
tooltip="Increase indent"
|
||||||
|
onClick={handleIndent}
|
||||||
|
icon={FormatIndentIncreaseIcon}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IncreaseIndentToolbarButton;
|
@ -0,0 +1,33 @@
|
|||||||
|
import { TableInsertColumn } from '@styled-icons/fluentui-system-regular/TableInsertColumn';
|
||||||
|
import { insertTableColumn } from '@udecode/plate';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useMdPlateEditorState } from '../../plateTypes';
|
||||||
|
import ToolbarButton from './common/ToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface InsertColumnToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const InsertColumnToolbarButton: FC<InsertColumnToolbarButtonProps> = ({ disabled, variant }) => {
|
||||||
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
|
const handleInsertTableColumn = useCallback(() => {
|
||||||
|
insertTableColumn(editor);
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
tooltip="Insert column"
|
||||||
|
icon={TableInsertColumn}
|
||||||
|
onClick={handleInsertTableColumn}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InsertColumnToolbarButton;
|
@ -1,26 +1,25 @@
|
|||||||
import { Image as ImageIcon } from '@styled-icons/material/Image';
|
import { Image as ImageIcon } from '@styled-icons/material/Image';
|
||||||
import { ELEMENT_IMAGE, insertImage } from '@udecode/plate';
|
import { insertImage } from '@udecode/plate';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import MenuItemButton from '@staticcms/core/components/common/menu/MenuItemButton';
|
|
||||||
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
|
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
|
||||||
import { isNotEmpty } from '@staticcms/core/lib/util/string.util';
|
import { isNotEmpty } from '@staticcms/core/lib/util/string.util';
|
||||||
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
||||||
import ToolbarButton from './ToolbarButton';
|
import ToolbarButton from './common/ToolbarButton';
|
||||||
|
|
||||||
import type { Collection, MarkdownField, MediaPath } from '@staticcms/core/interface';
|
import type { Collection, MarkdownField, MediaPath } from '@staticcms/core/interface';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
interface ImageToolbarButtonProps {
|
export interface InsertImageToolbarButtonProps {
|
||||||
variant?: 'button' | 'menu';
|
variant: 'button' | 'menu';
|
||||||
currentValue?: { url: string; alt?: string };
|
currentValue?: { url: string; alt?: string };
|
||||||
collection: Collection<MarkdownField>;
|
collection: Collection<MarkdownField>;
|
||||||
field: MarkdownField;
|
field: MarkdownField;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImageToolbarButton: FC<ImageToolbarButtonProps> = ({
|
const InsertImageToolbarButton: FC<InsertImageToolbarButtonProps> = ({
|
||||||
variant = 'button',
|
variant,
|
||||||
field,
|
field,
|
||||||
collection,
|
collection,
|
||||||
currentValue,
|
currentValue,
|
||||||
@ -47,23 +46,16 @@ const ImageToolbarButton: FC<ImageToolbarButtonProps> = ({
|
|||||||
handleInsert,
|
handleInsert,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (variant === 'menu') {
|
|
||||||
return (
|
|
||||||
<MenuItemButton key={ELEMENT_IMAGE} onClick={openMediaLibrary} startIcon={ImageIcon}>
|
|
||||||
Image
|
|
||||||
</MenuItemButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
key="insertImage"
|
label="Image"
|
||||||
tooltip="Insert Image"
|
tooltip="Insert image"
|
||||||
icon={<ImageIcon className="w-5 h-5" />}
|
icon={ImageIcon}
|
||||||
onClick={(_editor, event) => openMediaLibrary(event)}
|
onClick={openMediaLibrary}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ImageToolbarButton;
|
export default InsertImageToolbarButton;
|
@ -1,27 +1,26 @@
|
|||||||
import { Link as LinkIcon } from '@styled-icons/material/Link';
|
import { Link as LinkIcon } from '@styled-icons/material/Link';
|
||||||
import { ELEMENT_LINK, insertLink, someNode } from '@udecode/plate';
|
import { ELEMENT_LINK, getSelectionText, insertLink, someNode } from '@udecode/plate';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import MenuItemButton from '@staticcms/core/components/common/menu/MenuItemButton';
|
|
||||||
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
|
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
|
||||||
import useUUID from '@staticcms/core/lib/hooks/useUUID';
|
import useUUID from '@staticcms/core/lib/hooks/useUUID';
|
||||||
import { isNotEmpty } from '@staticcms/core/lib/util/string.util';
|
import { isNotEmpty } from '@staticcms/core/lib/util/string.util';
|
||||||
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
||||||
import ToolbarButton from './ToolbarButton';
|
import ToolbarButton from './common/ToolbarButton';
|
||||||
|
|
||||||
import type { Collection, MarkdownField, MediaPath } from '@staticcms/core/interface';
|
import type { Collection, MarkdownField, MediaPath } from '@staticcms/core/interface';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
interface LinkToolbarButtonProps {
|
export interface InsertLinkToolbarButtonProps {
|
||||||
variant?: 'button' | 'menu';
|
variant: 'button' | 'menu';
|
||||||
currentValue?: { url: string; alt?: string };
|
currentValue?: { url: string; alt?: string };
|
||||||
collection: Collection<MarkdownField>;
|
collection: Collection<MarkdownField>;
|
||||||
field: MarkdownField;
|
field: MarkdownField;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinkToolbarButton: FC<LinkToolbarButtonProps> = ({
|
const InsertLinkToolbarButton: FC<InsertLinkToolbarButtonProps> = ({
|
||||||
variant = 'button',
|
variant,
|
||||||
field,
|
field,
|
||||||
collection,
|
collection,
|
||||||
currentValue,
|
currentValue,
|
||||||
@ -45,34 +44,35 @@ const LinkToolbarButton: FC<LinkToolbarButtonProps> = ({
|
|||||||
|
|
||||||
const isLink = !!editor?.selection && someNode(editor, { match: { type: ELEMENT_LINK } });
|
const isLink = !!editor?.selection && someNode(editor, { match: { type: ELEMENT_LINK } });
|
||||||
|
|
||||||
|
const selectedText: string = useMemo(() => {
|
||||||
|
if (!editor.selection) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSelectionText(editor);
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
const controlID = useUUID();
|
const controlID = useUUID();
|
||||||
const openMediaLibrary = useMediaInsert(
|
const openMediaLibrary = useMediaInsert(
|
||||||
{
|
{
|
||||||
path: currentValue?.url ?? '',
|
path: currentValue?.url ?? '',
|
||||||
alt: currentValue?.alt,
|
alt: currentValue?.alt ?? selectedText,
|
||||||
},
|
},
|
||||||
{ collection, field, controlID, forImage: false, insertOptions: { chooseUrl, showAlt: true } },
|
{ collection, field, controlID, forImage: false, insertOptions: { chooseUrl, showAlt: true } },
|
||||||
handleInsert,
|
handleInsert,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (variant === 'menu') {
|
|
||||||
return (
|
|
||||||
<MenuItemButton key={ELEMENT_LINK} onClick={openMediaLibrary} startIcon={LinkIcon}>
|
|
||||||
File / Link
|
|
||||||
</MenuItemButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
key="insertLink"
|
label="Link"
|
||||||
tooltip="Insert Link"
|
tooltip="Insert link"
|
||||||
icon={<LinkIcon className="w-5 h-5" />}
|
icon={LinkIcon}
|
||||||
onClick={(_editor, event) => openMediaLibrary(event)}
|
onClick={openMediaLibrary}
|
||||||
active={isLink}
|
active={isLink}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LinkToolbarButton;
|
export default InsertLinkToolbarButton;
|
@ -0,0 +1,33 @@
|
|||||||
|
import { TableInsertRow } from '@styled-icons/fluentui-system-regular/TableInsertRow';
|
||||||
|
import { insertTableRow } from '@udecode/plate';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useMdPlateEditorState } from '../../plateTypes';
|
||||||
|
import ToolbarButton from './common/ToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface InsertRowToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const InsertRowToolbarButton: FC<InsertRowToolbarButtonProps> = ({ disabled, variant }) => {
|
||||||
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
|
const handleInsertTableRow = useCallback(() => {
|
||||||
|
insertTableRow(editor);
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
tooltip="Insert row"
|
||||||
|
icon={TableInsertRow}
|
||||||
|
onClick={handleInsertTableRow}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InsertRowToolbarButton;
|
@ -0,0 +1,40 @@
|
|||||||
|
import { TableAdd } from '@styled-icons/fluentui-system-regular/TableAdd';
|
||||||
|
import { insertTable } from '@udecode/plate';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useMdPlateEditorState } from '../../plateTypes';
|
||||||
|
import ToolbarButton from './common/ToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface InsertTableToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const InsertTableToolbarButton: FC<InsertTableToolbarButtonProps> = ({
|
||||||
|
disabled,
|
||||||
|
variant = 'button',
|
||||||
|
}) => {
|
||||||
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
|
const handleTableAdd = useCallback(() => {
|
||||||
|
insertTable(editor, {
|
||||||
|
rowCount: 2,
|
||||||
|
colCount: 2,
|
||||||
|
});
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
label="Table"
|
||||||
|
tooltip="Insert table"
|
||||||
|
icon={TableAdd}
|
||||||
|
onClick={handleTableAdd}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InsertTableToolbarButton;
|
@ -0,0 +1,26 @@
|
|||||||
|
import { FormatItalic as FormatItalicIcon } from '@styled-icons/material/FormatItalic';
|
||||||
|
import { MARK_ITALIC } from '@udecode/plate';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import MarkToolbarButton from './common/MarkToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface ItalicToolbarButtonsProp {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItalicToolbarButton: FC<ItalicToolbarButtonsProp> = ({ disabled, variant }) => {
|
||||||
|
return (
|
||||||
|
<MarkToolbarButton
|
||||||
|
tooltip="Italic"
|
||||||
|
type={MARK_ITALIC}
|
||||||
|
variant={variant}
|
||||||
|
icon={FormatItalicIcon}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ItalicToolbarButton;
|
@ -1,60 +0,0 @@
|
|||||||
import { FormatIndentDecrease as FormatIndentDecreaseIcon } from '@styled-icons/material/FormatIndentDecrease';
|
|
||||||
import { FormatIndentIncrease as FormatIndentIncreaseIcon } from '@styled-icons/material/FormatIndentIncrease';
|
|
||||||
import { FormatListBulleted as FormatListBulletedIcon } from '@styled-icons/material/FormatListBulleted';
|
|
||||||
import { FormatListNumbered as FormatListNumberedIcon } from '@styled-icons/material/FormatListNumbered';
|
|
||||||
import { ELEMENT_OL, ELEMENT_UL, getPluginType, indent, outdent } from '@udecode/plate';
|
|
||||||
import React, { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { useMdPlateEditorRef } from '@staticcms/markdown';
|
|
||||||
import ListToolbarButton from './common/ListToolbarButton';
|
|
||||||
import ToolbarButton from './common/ToolbarButton';
|
|
||||||
|
|
||||||
import type { FC } from 'react';
|
|
||||||
import type { MdEditor } from '@staticcms/markdown';
|
|
||||||
|
|
||||||
interface ListToolbarButtonsProps {
|
|
||||||
disabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ListToolbarButtons: FC<ListToolbarButtonsProps> = ({ disabled }) => {
|
|
||||||
const editor = useMdPlateEditorRef();
|
|
||||||
|
|
||||||
const handleOutdent = useCallback((editor: MdEditor) => {
|
|
||||||
outdent(editor);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleIndent = useCallback((editor: MdEditor) => {
|
|
||||||
indent(editor);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ListToolbarButton
|
|
||||||
tooltip="List"
|
|
||||||
type={ELEMENT_UL}
|
|
||||||
icon={<FormatListBulletedIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<ListToolbarButton
|
|
||||||
tooltip="Numbered List"
|
|
||||||
type={getPluginType(editor, ELEMENT_OL)}
|
|
||||||
icon={<FormatListNumberedIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
tooltip="Outdent"
|
|
||||||
onClick={handleOutdent}
|
|
||||||
icon={<FormatIndentDecreaseIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
tooltip="Indent"
|
|
||||||
onClick={handleIndent}
|
|
||||||
icon={<FormatIndentIncreaseIcon className="h-5 w-5" />}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ListToolbarButtons;
|
|
@ -1,42 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import ImageToolbarButton from './common/ImageToolbarButton';
|
|
||||||
import LinkToolbarButton from './common/LinkToolbarButton';
|
|
||||||
|
|
||||||
import type { Collection, MarkdownField } from '@staticcms/core/interface';
|
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
export interface MediaToolbarButtonsProps {
|
|
||||||
collection: Collection<MarkdownField>;
|
|
||||||
field: MarkdownField;
|
|
||||||
hideImages?: boolean;
|
|
||||||
disabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MediaToolbarButtons: FC<MediaToolbarButtonsProps> = ({
|
|
||||||
collection,
|
|
||||||
field,
|
|
||||||
hideImages = false,
|
|
||||||
disabled,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LinkToolbarButton
|
|
||||||
key="link-button"
|
|
||||||
collection={collection}
|
|
||||||
field={field}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
{!hideImages ? (
|
|
||||||
<ImageToolbarButton
|
|
||||||
key="image-button"
|
|
||||||
collection={collection}
|
|
||||||
field={field}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MediaToolbarButtons;
|
|
@ -0,0 +1,26 @@
|
|||||||
|
import { FormatListNumbered as FormatListNumberedIcon } from '@styled-icons/material/FormatListNumbered';
|
||||||
|
import { ELEMENT_OL } from '@udecode/plate';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import ListToolbarButton from './common/ListToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface OrderedListToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderedListToolbarButton: FC<OrderedListToolbarButtonProps> = ({ disabled, variant }) => {
|
||||||
|
return (
|
||||||
|
<ListToolbarButton
|
||||||
|
tooltip="Numbered list"
|
||||||
|
type={ELEMENT_OL}
|
||||||
|
icon={FormatListNumberedIcon}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrderedListToolbarButton;
|
@ -11,7 +11,7 @@ import { ELEMENT_SHORTCODE, useMdPlateEditorState } from '@staticcms/markdown/pl
|
|||||||
|
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
interface ShortcodeToolbarButtonProps {
|
export interface ShortcodeToolbarButtonProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ const ShortcodeToolbarButton: FC<ShortcodeToolbarButtonProps> = ({ disabled }) =
|
|||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
label={<DataArrayIcon className="h-5 w-5" aria-hidden="true" />}
|
label={<DataArrayIcon className="h-5 w-5" aria-hidden="true" />}
|
||||||
data-testid="add-buttons"
|
data-testid="toolbar-button-shortcode"
|
||||||
keepMounted
|
keepMounted
|
||||||
hideDropdownIcon
|
hideDropdownIcon
|
||||||
variant="text"
|
variant="text"
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import { FormatStrikethrough as FormatStrikethroughIcon } from '@styled-icons/material/FormatStrikethrough';
|
||||||
|
import { MARK_STRIKETHROUGH } from '@udecode/plate';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import MarkToolbarButton from './common/MarkToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface StrikethroughToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const StrikethroughToolbarButton: FC<StrikethroughToolbarButtonProps> = ({ disabled, variant }) => {
|
||||||
|
return (
|
||||||
|
<MarkToolbarButton
|
||||||
|
tooltip="Strikethrough"
|
||||||
|
type={MARK_STRIKETHROUGH}
|
||||||
|
variant={variant}
|
||||||
|
icon={FormatStrikethroughIcon}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StrikethroughToolbarButton;
|
@ -1,104 +0,0 @@
|
|||||||
import { TableAdd } from '@styled-icons/fluentui-system-regular/TableAdd';
|
|
||||||
import { TableDeleteColumn } from '@styled-icons/fluentui-system-regular/TableDeleteColumn';
|
|
||||||
import { TableDeleteRow } from '@styled-icons/fluentui-system-regular/TableDeleteRow';
|
|
||||||
import { TableDismiss } from '@styled-icons/fluentui-system-regular/TableDismiss';
|
|
||||||
import { TableInsertColumn } from '@styled-icons/fluentui-system-regular/TableInsertColumn';
|
|
||||||
import { TableInsertRow } from '@styled-icons/fluentui-system-regular/TableInsertRow';
|
|
||||||
import {
|
|
||||||
deleteColumn,
|
|
||||||
deleteRow,
|
|
||||||
deleteTable,
|
|
||||||
insertTable,
|
|
||||||
insertTableColumn,
|
|
||||||
insertTableRow,
|
|
||||||
} from '@udecode/plate';
|
|
||||||
import React, { useCallback } from 'react';
|
|
||||||
|
|
||||||
import ToolbarButton from './common/ToolbarButton';
|
|
||||||
|
|
||||||
import type { FC } from 'react';
|
|
||||||
import type { MdEditor } from '@staticcms/markdown';
|
|
||||||
|
|
||||||
export interface TableToolbarButtonsProps {
|
|
||||||
isInTable?: boolean;
|
|
||||||
disabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TableToolbarButtons: FC<TableToolbarButtonsProps> = ({ isInTable = true, disabled }) => {
|
|
||||||
const handleTableAdd = useCallback((editor: MdEditor) => {
|
|
||||||
insertTable(editor, {
|
|
||||||
rowCount: 2,
|
|
||||||
colCount: 2,
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleInsertTableRow = useCallback((editor: MdEditor) => {
|
|
||||||
insertTableRow(editor);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDeleteRow = useCallback((editor: MdEditor) => {
|
|
||||||
deleteRow(editor);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleInsertTableColumn = useCallback((editor: MdEditor) => {
|
|
||||||
insertTableColumn(editor);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDeleteColumn = useCallback((editor: MdEditor) => {
|
|
||||||
deleteColumn(editor);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDeleteTable = useCallback((editor: MdEditor) => {
|
|
||||||
deleteTable(editor);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return isInTable ? (
|
|
||||||
<>
|
|
||||||
<ToolbarButton
|
|
||||||
key="insertRow"
|
|
||||||
tooltip="Insert Row"
|
|
||||||
icon={<TableInsertRow className="w-5 h-5" />}
|
|
||||||
onClick={handleInsertTableRow}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
key="deleteRow"
|
|
||||||
tooltip="Delete Row"
|
|
||||||
icon={<TableDeleteRow className="w-5 h-5" />}
|
|
||||||
onClick={handleDeleteRow}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
key="insertColumn"
|
|
||||||
tooltip="Insert Column"
|
|
||||||
icon={<TableInsertColumn className="w-5 h-5" />}
|
|
||||||
onClick={handleInsertTableColumn}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
key="deleteColumn"
|
|
||||||
tooltip="Delete Column"
|
|
||||||
icon={<TableDeleteColumn className="w-5 h-5" />}
|
|
||||||
onClick={handleDeleteColumn}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
key="deleteTable"
|
|
||||||
tooltip="Delete Table"
|
|
||||||
icon={<TableDismiss className="w-5 h-5" />}
|
|
||||||
onClick={handleDeleteTable}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<ToolbarButton
|
|
||||||
key="insertRow"
|
|
||||||
tooltip="Add Table"
|
|
||||||
icon={<TableAdd className="w-5 h-5" />}
|
|
||||||
onClick={handleTableAdd}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TableToolbarButtons;
|
|
@ -0,0 +1,26 @@
|
|||||||
|
import { FormatListBulleted as FormatListBulletedIcon } from '@styled-icons/material/FormatListBulleted';
|
||||||
|
import { ELEMENT_UL } from '@udecode/plate';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import ListToolbarButton from './common/ListToolbarButton';
|
||||||
|
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
export interface UnorderedListToolbarButtonProps {
|
||||||
|
disabled: boolean;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
const UnorderedListToolbarButton: FC<UnorderedListToolbarButtonProps> = ({ disabled, variant }) => {
|
||||||
|
return (
|
||||||
|
<ListToolbarButton
|
||||||
|
tooltip="List"
|
||||||
|
type={ELEMENT_UL}
|
||||||
|
icon={FormatListBulletedIcon}
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UnorderedListToolbarButton;
|
@ -4,7 +4,6 @@ import React, { useCallback } from 'react';
|
|||||||
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
||||||
import ToolbarButton from './ToolbarButton';
|
import ToolbarButton from './ToolbarButton';
|
||||||
|
|
||||||
import type { MdEditor } from '@staticcms/markdown';
|
|
||||||
import type { Alignment } from '@udecode/plate';
|
import type { Alignment } from '@udecode/plate';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import type { ToolbarButtonProps } from './ToolbarButton';
|
import type { ToolbarButtonProps } from './ToolbarButton';
|
||||||
@ -12,6 +11,7 @@ import type { ToolbarButtonProps } from './ToolbarButton';
|
|||||||
export interface AlignToolbarButtonProps extends Omit<ToolbarButtonProps, 'active' | 'onClick'> {
|
export interface AlignToolbarButtonProps extends Omit<ToolbarButtonProps, 'active' | 'onClick'> {
|
||||||
value: Alignment;
|
value: Alignment;
|
||||||
pluginKey?: string;
|
pluginKey?: string;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
}
|
}
|
||||||
|
|
||||||
const AlignToolbarButton: FC<AlignToolbarButtonProps> = ({
|
const AlignToolbarButton: FC<AlignToolbarButtonProps> = ({
|
||||||
@ -21,15 +21,12 @@ const AlignToolbarButton: FC<AlignToolbarButtonProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const editor = useMdPlateEditorState();
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
const handleOnClick = useCallback(
|
const handleOnClick = useCallback(() => {
|
||||||
(editor: MdEditor) => {
|
setAlign(editor, {
|
||||||
setAlign(editor, {
|
value,
|
||||||
value,
|
key: pluginKey,
|
||||||
key: pluginKey,
|
});
|
||||||
});
|
}, [editor, pluginKey, value]);
|
||||||
},
|
|
||||||
[pluginKey, value],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
|
@ -4,35 +4,33 @@ import React, { useCallback } from 'react';
|
|||||||
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
||||||
import ToolbarButton from './ToolbarButton';
|
import ToolbarButton from './ToolbarButton';
|
||||||
|
|
||||||
import type { MdEditor } from '@staticcms/markdown';
|
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import type { ToolbarButtonProps } from './ToolbarButton';
|
import type { ToolbarButtonProps } from './ToolbarButton';
|
||||||
|
|
||||||
export interface BlockToolbarButtonProps extends Omit<ToolbarButtonProps, 'active' | 'onClick'> {
|
export interface BlockToolbarButtonProps extends Omit<ToolbarButtonProps, 'active' | 'onClick'> {
|
||||||
type: string;
|
type: string;
|
||||||
inactiveType?: string;
|
inactiveType?: string;
|
||||||
onClick?: (editor: MdEditor) => void;
|
variant: 'button' | 'menu';
|
||||||
}
|
}
|
||||||
|
|
||||||
const BlockToolbarButton: FC<BlockToolbarButtonProps> = ({
|
const BlockToolbarButton: FC<BlockToolbarButtonProps> = ({
|
||||||
type,
|
type,
|
||||||
inactiveType,
|
inactiveType,
|
||||||
onClick,
|
icon,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const editor = useMdPlateEditorState();
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
const handleOnClick = useCallback(
|
const handleOnClick = useCallback(() => {
|
||||||
(editor: MdEditor) => {
|
toggleNodeType(editor, { activeType: type, inactiveType });
|
||||||
toggleNodeType(editor, { activeType: type, inactiveType });
|
}, [editor, inactiveType, type]);
|
||||||
},
|
|
||||||
[inactiveType, type],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
|
key={type}
|
||||||
active={!!editor?.selection && someNode(editor, { match: { type } })}
|
active={!!editor?.selection && someNode(editor, { match: { type } })}
|
||||||
onClick={onClick ?? handleOnClick}
|
onClick={handleOnClick}
|
||||||
|
icon={icon}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -4,30 +4,32 @@ import React, { useCallback } from 'react';
|
|||||||
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
||||||
import ToolbarButton from './ToolbarButton';
|
import ToolbarButton from './ToolbarButton';
|
||||||
|
|
||||||
import type { MdEditor } from '@staticcms/markdown';
|
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import type { ToolbarButtonProps } from './ToolbarButton';
|
import type { ToolbarButtonProps } from './ToolbarButton';
|
||||||
|
|
||||||
export interface ListToolbarButtonProps extends Omit<ToolbarButtonProps, 'active' | 'onClick'> {
|
export interface ListToolbarButtonProps extends Omit<ToolbarButtonProps, 'active' | 'onClick'> {
|
||||||
type: string;
|
type: string;
|
||||||
|
variant: 'button' | 'menu';
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListToolbarButton: FC<ListToolbarButtonProps> = ({ type, ...props }) => {
|
const ListToolbarButton: FC<ListToolbarButtonProps> = ({ type, icon, ...props }) => {
|
||||||
const editor = useMdPlateEditorState();
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
const handleOnClick = useCallback(
|
const handleOnClick = useCallback(() => {
|
||||||
(editor: MdEditor) => {
|
toggleList(editor, {
|
||||||
toggleList(editor, {
|
type,
|
||||||
type,
|
});
|
||||||
});
|
}, [editor, type]);
|
||||||
},
|
|
||||||
[type],
|
|
||||||
);
|
|
||||||
|
|
||||||
const res = !!editor?.selection && getListItemEntry(editor);
|
const res = !!editor?.selection && getListItemEntry(editor);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolbarButton active={!!res && res.list[0].type === type} onClick={handleOnClick} {...props} />
|
<ToolbarButton
|
||||||
|
active={!!res && res.list[0].type === type}
|
||||||
|
onClick={handleOnClick}
|
||||||
|
icon={icon}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,29 +4,29 @@ import React, { useCallback } from 'react';
|
|||||||
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
||||||
import ToolbarButton from './ToolbarButton';
|
import ToolbarButton from './ToolbarButton';
|
||||||
|
|
||||||
import type { MdEditor } from '@staticcms/markdown';
|
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import type { ToolbarButtonProps } from './ToolbarButton';
|
import type { ToolbarButtonProps } from './ToolbarButton';
|
||||||
|
|
||||||
export interface MarkToolbarButtonProps extends Omit<ToolbarButtonProps, 'active' | 'onClick'> {
|
export interface MarkToolbarButtonProps
|
||||||
|
extends Omit<ToolbarButtonProps, 'active' | 'onClick' | 'icon'> {
|
||||||
type: string;
|
type: string;
|
||||||
clear?: string | string[];
|
clear?: string | string[];
|
||||||
|
variant: 'button' | 'menu';
|
||||||
|
icon: FC<{ className?: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MarkToolbarButton: FC<MarkToolbarButtonProps> = ({ type, clear, ...props }) => {
|
const MarkToolbarButton: FC<MarkToolbarButtonProps> = ({ type, clear, icon, ...props }) => {
|
||||||
const editor = useMdPlateEditorState();
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
const handleOnClick = useCallback(
|
const handleOnClick = useCallback(() => {
|
||||||
(editor: MdEditor) => {
|
toggleMark(editor, { key: type, clear });
|
||||||
toggleMark(editor, { key: type, clear });
|
}, [clear, editor, type]);
|
||||||
},
|
|
||||||
[clear, type],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
active={!!editor?.selection && isMarkActive(editor, type)}
|
active={!!editor?.selection && isMarkActive(editor, type)}
|
||||||
onClick={handleOnClick}
|
onClick={handleOnClick}
|
||||||
|
icon={icon}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -2,44 +2,46 @@ import { focusEditor } from '@udecode/plate';
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import Button from '@staticcms/core/components/common/button/Button';
|
import Button from '@staticcms/core/components/common/button/Button';
|
||||||
|
import MenuItemButton from '@staticcms/core/components/common/menu/MenuItemButton';
|
||||||
import classNames from '@staticcms/core/lib/util/classNames.util';
|
import classNames from '@staticcms/core/lib/util/classNames.util';
|
||||||
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
|
||||||
|
|
||||||
import type { MdEditor } from '@staticcms/markdown';
|
import type { CSSProperties, FC, MouseEvent } from 'react';
|
||||||
import type { CSSProperties, FC, MouseEvent, ReactNode } from 'react';
|
|
||||||
|
|
||||||
export interface ToolbarButtonProps {
|
export interface ToolbarButtonProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
activeColor?: string;
|
activeColor?: string;
|
||||||
icon: ReactNode;
|
icon: FC<{ className?: string }>;
|
||||||
disableFocusAfterClick?: boolean;
|
disableFocusAfterClick?: boolean;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
onClick: (editor: MdEditor, event: MouseEvent<HTMLButtonElement>) => void;
|
variant: 'button' | 'menu';
|
||||||
|
onClick: (event: MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToolbarButton: FC<ToolbarButtonProps> = ({
|
const ToolbarButton: FC<ToolbarButtonProps> = ({
|
||||||
icon,
|
icon: Icon,
|
||||||
tooltip,
|
tooltip,
|
||||||
label,
|
label,
|
||||||
active = false,
|
active = false,
|
||||||
activeColor,
|
activeColor,
|
||||||
disableFocusAfterClick = false,
|
disableFocusAfterClick = false,
|
||||||
disabled,
|
disabled,
|
||||||
|
variant,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
const editor = useMdPlateEditorState();
|
const editor = useMdPlateEditorState();
|
||||||
|
|
||||||
const handleOnClick = useCallback(
|
const handleOnClick = useCallback(
|
||||||
(event: MouseEvent<HTMLButtonElement>) => {
|
(event: MouseEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick(editor, event);
|
onClick(event);
|
||||||
|
|
||||||
if (!disableFocusAfterClick) {
|
if (!disableFocusAfterClick) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -55,8 +57,17 @@ const ToolbarButton: FC<ToolbarButtonProps> = ({
|
|||||||
style.color = activeColor;
|
style.color = activeColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (variant === 'menu') {
|
||||||
|
return (
|
||||||
|
<MenuItemButton key="menu-item" onClick={handleOnClick} startIcon={Icon}>
|
||||||
|
{label ?? tooltip}
|
||||||
|
</MenuItemButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
key="button"
|
||||||
aria-label={label ?? tooltip}
|
aria-label={label ?? tooltip}
|
||||||
variant="text"
|
variant="text"
|
||||||
data-testid={`toolbar-button-${label ?? tooltip}`.replace(' ', '-').toLowerCase()}
|
data-testid={`toolbar-button-${label ?? tooltip}`.replace(' ', '-').toLowerCase()}
|
||||||
@ -78,7 +89,7 @@ const ToolbarButton: FC<ToolbarButtonProps> = ({
|
|||||||
style={style}
|
style={style}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{icon}
|
{<Icon className="w-5 h-5" />}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -5,8 +5,6 @@ export { default as BlockToolbarButton } from './BlockToolbarButton';
|
|||||||
export * from './ColorPickerToolbarDropdown';
|
export * from './ColorPickerToolbarDropdown';
|
||||||
export { default as ColorPickerToolbarDropdown } from './ColorPickerToolbarDropdown';
|
export { default as ColorPickerToolbarDropdown } from './ColorPickerToolbarDropdown';
|
||||||
export * from './dropdown';
|
export * from './dropdown';
|
||||||
export { default as ImageToolbarButton } from './ImageToolbarButton';
|
|
||||||
export { default as LinkToolbarButton } from './LinkToolbarButton';
|
|
||||||
export * from './ListToolbarButton';
|
export * from './ListToolbarButton';
|
||||||
export { default as ListToolbarButton } from './ListToolbarButton';
|
export { default as ListToolbarButton } from './ListToolbarButton';
|
||||||
export * from './MarkToolbarButton';
|
export * from './MarkToolbarButton';
|
||||||
|
@ -1,14 +1,32 @@
|
|||||||
export { default as AlignToolbarButtons } from './AlignToolbarButtons';
|
export * from './BoldToolbarButton';
|
||||||
export * from './BasicElementToolbarButtons';
|
export { default as BoldToolbarButton } from './BoldToolbarButton';
|
||||||
export { default as BasicElementToolbarButtons } from './BasicElementToolbarButtons';
|
export * from './DecreaseIndentToolbarButton';
|
||||||
export * from './BasicMarkToolbarButtons';
|
export { default as IncreaseIndentToolbarButton } from './DecreaseIndentToolbarButton';
|
||||||
export { default as BasicMarkToolbarButtons } from './BasicMarkToolbarButtons';
|
export * from './DeleteColumnToolbarButton';
|
||||||
export { default as ColorToolbarButtons } from './ColorToolbarButtons';
|
export { default as DeleteColumnToolbarButton } from './DeleteColumnToolbarButton';
|
||||||
export * from './common';
|
export * from './DeleteRowToolbarButton';
|
||||||
|
export { default as DeleteRowToolbarButton } from './DeleteRowToolbarButton';
|
||||||
|
export * from './DeleteTableToolbarButton';
|
||||||
|
export { default as DeleteTableToolbarButton } from './DeleteTableToolbarButton';
|
||||||
export * from './FontTypeSelect';
|
export * from './FontTypeSelect';
|
||||||
export { default as FontTypeSelect } from './FontTypeSelect';
|
export { default as FontTypeSelect } from './FontTypeSelect';
|
||||||
export { default as ListToolbarButtons } from './ListToolbarButtons';
|
export * from './InsertImageToolbarButton';
|
||||||
export * from './MediaToolbarButtons';
|
export { default as ImageToolbarButton } from './InsertImageToolbarButton';
|
||||||
export { default as MediaToolbarButtons } from './MediaToolbarButtons';
|
export * from './IncreaseIndentToolbarButton';
|
||||||
export * from './TableToolbarButtons';
|
export { default as DecreaseIndentToolbarButton } from './IncreaseIndentToolbarButton';
|
||||||
export { default as TableToolbarButtons } from './TableToolbarButtons';
|
export * from './InsertColumnToolbarButton';
|
||||||
|
export { default as InsertColumnToolbarButton } from './InsertColumnToolbarButton';
|
||||||
|
export * from './InsertRowToolbarButton';
|
||||||
|
export { default as InsertRowToolbarButton } from './InsertRowToolbarButton';
|
||||||
|
export * from './ItalicToolbarButton';
|
||||||
|
export { default as ItalicToolbarButton } from './ItalicToolbarButton';
|
||||||
|
export * from './InsertLinkToolbarButton';
|
||||||
|
export { default as LinkToolbarButton } from './InsertLinkToolbarButton';
|
||||||
|
export * from './OrderedListToolbarButton';
|
||||||
|
export { default as OrderedListToolbarButton } from './OrderedListToolbarButton';
|
||||||
|
export * from './ShortcodeToolbarButton';
|
||||||
|
export { default as ShortcodeToolbarButton } from './ShortcodeToolbarButton';
|
||||||
|
export * from './StrikethroughToolbarButton';
|
||||||
|
export { default as StrikethroughToolbarButton } from './StrikethroughToolbarButton';
|
||||||
|
export * from './UnorderedListToolbarButton';
|
||||||
|
export { default as UnorderedListToolbarButton } from './UnorderedListToolbarButton';
|
||||||
|
@ -1,16 +1,54 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import AddButtons from '../buttons/AddButtons';
|
import useToolbarButtons from '../../hooks/useToolbarButtons';
|
||||||
import AlignToolbarButtons from '../buttons/AlignToolbarButtons';
|
import {
|
||||||
import BasicElementToolbarButtons from '../buttons/BasicElementToolbarButtons';
|
BOLD_TOOLBAR_BUTTON,
|
||||||
import BasicMarkToolbarButtons from '../buttons/BasicMarkToolbarButtons';
|
ITALIC_TOOLBAR_BUTTON,
|
||||||
import ColorToolbarButtons from '../buttons/ColorToolbarButtons';
|
STRIKETHROUGH_TOOLBAR_BUTTON,
|
||||||
import ListToolbarButtons from '../buttons/ListToolbarButtons';
|
CODE_TOOLBAR_BUTTON,
|
||||||
import ShortcodeToolbarButton from '../buttons/ShortcodeToolbarButton';
|
FONT_TOOLBAR_BUTTON,
|
||||||
|
IMAGE_TOOLBAR_BUTTON,
|
||||||
|
FILE_LINK_TOOLBAR_BUTTON,
|
||||||
|
INSERT_TABLE_TOOLBAR_BUTTON,
|
||||||
|
BLOCKQUOTE_TOOLBAR_BUTTON,
|
||||||
|
SHORTCODE_TOOLBAR_BUTTON,
|
||||||
|
INCRASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
DECREASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
ORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
UNORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
CODE_BLOCK_TOOLBAR_BUTTON,
|
||||||
|
} from '@staticcms/core/constants/toolbar_buttons';
|
||||||
|
|
||||||
import type { Collection, MarkdownField } from '@staticcms/core/interface';
|
import type { Collection, MarkdownField, MarkdownToolbarItem } from '@staticcms/core/interface';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
const DEFAULT_TOOLBAR_BUTTONS: MarkdownToolbarItem[] = [
|
||||||
|
BOLD_TOOLBAR_BUTTON,
|
||||||
|
ITALIC_TOOLBAR_BUTTON,
|
||||||
|
STRIKETHROUGH_TOOLBAR_BUTTON,
|
||||||
|
CODE_TOOLBAR_BUTTON,
|
||||||
|
FONT_TOOLBAR_BUTTON,
|
||||||
|
UNORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
ORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
DECREASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
INCRASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
SHORTCODE_TOOLBAR_BUTTON,
|
||||||
|
{
|
||||||
|
label: 'Insert',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
items: [BLOCKQUOTE_TOOLBAR_BUTTON, CODE_BLOCK_TOOLBAR_BUTTON],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: [INSERT_TABLE_TOOLBAR_BUTTON],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: [IMAGE_TOOLBAR_BUTTON, FILE_LINK_TOOLBAR_BUTTON],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export interface ToolbarProps {
|
export interface ToolbarProps {
|
||||||
useMdx: boolean;
|
useMdx: boolean;
|
||||||
collection: Collection<MarkdownField>;
|
collection: Collection<MarkdownField>;
|
||||||
@ -18,21 +56,13 @@ export interface ToolbarProps {
|
|||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Toolbar: FC<ToolbarProps> = ({ useMdx, collection, field, disabled }) => {
|
const Toolbar: FC<ToolbarProps> = ({ collection, field, disabled }) => {
|
||||||
const groups = [
|
const buttons = useToolbarButtons(
|
||||||
<BasicMarkToolbarButtons
|
field.toolbar_buttons?.main ?? DEFAULT_TOOLBAR_BUTTONS,
|
||||||
key="basic-mark-buttons"
|
collection,
|
||||||
useMdx={useMdx}
|
field,
|
||||||
extended
|
disabled,
|
||||||
disabled={disabled}
|
);
|
||||||
/>,
|
|
||||||
<BasicElementToolbarButtons key="basic-element-buttons" disabled={disabled} />,
|
|
||||||
<ListToolbarButtons key="list-buttons" disabled={disabled} />,
|
|
||||||
useMdx ? <ColorToolbarButtons key="color-buttons" disabled={disabled} /> : null,
|
|
||||||
useMdx ? <AlignToolbarButtons key="align-mark-buttons" disabled={disabled} /> : null,
|
|
||||||
!useMdx ? <ShortcodeToolbarButton key="shortcode-button" disabled={disabled} /> : null,
|
|
||||||
<AddButtons key="add-buttons" collection={collection} field={field} disabled={disabled} />,
|
|
||||||
].filter(Boolean);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -59,7 +89,7 @@ const Toolbar: FC<ToolbarProps> = ({ useMdx, collection, field, disabled }) => {
|
|||||||
z-10
|
z-10
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{groups}
|
{buttons}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,206 @@
|
|||||||
|
import { Add as AddIcon } from '@styled-icons/material/Add';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
import Menu from '@staticcms/core/components/common/menu/Menu';
|
||||||
|
import MenuGroup from '@staticcms/core/components/common/menu/MenuGroup';
|
||||||
|
import BlockquoteToolbarButton from '../components/buttons/BlockquoteToolbarButton';
|
||||||
|
import BoldToolbarButton from '../components/buttons/BoldToolbarButton';
|
||||||
|
import CodeBlockToolbarButtons from '../components/buttons/CodeBlockToolbarButtons';
|
||||||
|
import CodeToolbarButton from '../components/buttons/CodeToolbarButton';
|
||||||
|
import IncreaseIndentButton from '../components/buttons/DecreaseIndentToolbarButton';
|
||||||
|
import DeleteColumnToolbarButton from '../components/buttons/DeleteColumnToolbarButton';
|
||||||
|
import DeleteRowToolbarButton from '../components/buttons/DeleteRowToolbarButton';
|
||||||
|
import DeleteTableToolbarButton from '../components/buttons/DeleteTableToolbarButton';
|
||||||
|
import FontTypeSelect from '../components/buttons/FontTypeSelect';
|
||||||
|
import DecreaseIndentButton from '../components/buttons/IncreaseIndentToolbarButton';
|
||||||
|
import InsertColumnToolbarButton from '../components/buttons/InsertColumnToolbarButton';
|
||||||
|
import InsertImageToolbarButton from '../components/buttons/InsertImageToolbarButton';
|
||||||
|
import InsertLinkToolbarButton from '../components/buttons/InsertLinkToolbarButton';
|
||||||
|
import InsertRowToolbarButton from '../components/buttons/InsertRowToolbarButton';
|
||||||
|
import InsertTableToolbarButton from '../components/buttons/InsertTableToolbarButton';
|
||||||
|
import ItalicToolbarButton from '../components/buttons/ItalicToolbarButton';
|
||||||
|
import OrderedListButton from '../components/buttons/OrderedListToolbarButton';
|
||||||
|
import ShortcodeToolbarButton from '../components/buttons/ShortcodeToolbarButton';
|
||||||
|
import StrikethroughToolbarButton from '../components/buttons/StrikethroughToolbarButton';
|
||||||
|
import UnorderedListButton from '../components/buttons/UnorderedListToolbarButton';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Collection,
|
||||||
|
LowLevelMarkdownToolbarButtonType,
|
||||||
|
MarkdownField,
|
||||||
|
MarkdownToolbarButtonType,
|
||||||
|
MarkdownToolbarItem,
|
||||||
|
} from '@staticcms/core/interface';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function useToolbarButtons(
|
||||||
|
toolbarButtons: MarkdownToolbarItem[],
|
||||||
|
collection: Collection<MarkdownField>,
|
||||||
|
field: MarkdownField,
|
||||||
|
disabled: boolean,
|
||||||
|
): ReactNode[] {
|
||||||
|
return useMemo(
|
||||||
|
() => getToolbarButtons(toolbarButtons, collection, field, disabled),
|
||||||
|
[collection, disabled, field, toolbarButtons],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToolbarButtons(
|
||||||
|
toolbarButtons: MarkdownToolbarItem[] | MarkdownToolbarButtonType[],
|
||||||
|
collection: Collection<MarkdownField>,
|
||||||
|
field: MarkdownField,
|
||||||
|
disabled: boolean,
|
||||||
|
): ReactNode[] {
|
||||||
|
return toolbarButtons.map(button => {
|
||||||
|
if (typeof button === 'string') {
|
||||||
|
return getToolbarButton(button, collection, field, disabled, 'button');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
key={`menu-${button.label}`}
|
||||||
|
label={<AddIcon className="h-5 w-5" aria-hidden="true" />}
|
||||||
|
data-testid={`toolbar-menu-${button.label.toLowerCase().replace(' ', '-')}`}
|
||||||
|
keepMounted
|
||||||
|
hideDropdownIcon
|
||||||
|
variant="text"
|
||||||
|
className="
|
||||||
|
py-0.5
|
||||||
|
px-0.5
|
||||||
|
h-6
|
||||||
|
w-6
|
||||||
|
"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{button.groups.map((group, index) => {
|
||||||
|
if (group.items.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuGroup key={`group-${index}`}>
|
||||||
|
{group.items.map(item => getToolbarButton(item, collection, field, disabled, 'menu'))}
|
||||||
|
</MenuGroup>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToolbarButton(
|
||||||
|
name: MarkdownToolbarButtonType,
|
||||||
|
collection: Collection<MarkdownField>,
|
||||||
|
field: MarkdownField,
|
||||||
|
disabled: boolean,
|
||||||
|
variant: 'button',
|
||||||
|
): ReactNode;
|
||||||
|
function getToolbarButton(
|
||||||
|
name: LowLevelMarkdownToolbarButtonType,
|
||||||
|
collection: Collection<MarkdownField>,
|
||||||
|
field: MarkdownField,
|
||||||
|
disabled: boolean,
|
||||||
|
variant: 'menu',
|
||||||
|
): ReactNode;
|
||||||
|
function getToolbarButton(
|
||||||
|
name: MarkdownToolbarButtonType | LowLevelMarkdownToolbarButtonType,
|
||||||
|
collection: Collection<MarkdownField>,
|
||||||
|
field: MarkdownField,
|
||||||
|
disabled: boolean,
|
||||||
|
variant: 'button' | 'menu',
|
||||||
|
): ReactNode {
|
||||||
|
switch (name) {
|
||||||
|
case 'blockquote':
|
||||||
|
return <BlockquoteToolbarButton key="bold" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'bold':
|
||||||
|
return <BoldToolbarButton key="bold" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'code':
|
||||||
|
return <CodeToolbarButton key="code" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'code-block':
|
||||||
|
return <CodeBlockToolbarButtons key="code" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'decrease-indent':
|
||||||
|
return <DecreaseIndentButton key="decrease-indent" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'delete-column':
|
||||||
|
return (
|
||||||
|
<DeleteColumnToolbarButton key="delete-column" disabled={disabled} variant={variant} />
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'delete-row':
|
||||||
|
return <DeleteRowToolbarButton key="delete-row" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'delete-table':
|
||||||
|
return <DeleteTableToolbarButton key="delete-table" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'font':
|
||||||
|
if (variant === 'menu') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <FontTypeSelect key="font" disabled={disabled} />;
|
||||||
|
|
||||||
|
case 'increase-indent':
|
||||||
|
return <IncreaseIndentButton key="increase-indent" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'insert-column':
|
||||||
|
return (
|
||||||
|
<InsertColumnToolbarButton key="insert-column" disabled={disabled} variant={variant} />
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'image':
|
||||||
|
return (
|
||||||
|
<InsertImageToolbarButton
|
||||||
|
key="image"
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
collection={collection}
|
||||||
|
field={field}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'file-link':
|
||||||
|
return (
|
||||||
|
<InsertLinkToolbarButton
|
||||||
|
key="file-link"
|
||||||
|
disabled={disabled}
|
||||||
|
variant={variant}
|
||||||
|
collection={collection}
|
||||||
|
field={field}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'insert-row':
|
||||||
|
return <InsertRowToolbarButton key="insert-row" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'insert-table':
|
||||||
|
return <InsertTableToolbarButton key="insert-table" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'italic':
|
||||||
|
return <ItalicToolbarButton key="italic" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'ordered-list':
|
||||||
|
return <OrderedListButton key="ordered-list" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
case 'shortcode':
|
||||||
|
if (variant === 'menu') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ShortcodeToolbarButton key="shortcode" disabled={disabled} />;
|
||||||
|
|
||||||
|
case 'strikethrough':
|
||||||
|
return (
|
||||||
|
<StrikethroughToolbarButton key="strikethrough" disabled={disabled} variant={variant} />
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'unordered-list':
|
||||||
|
return <UnorderedListButton key="unordered-list" disabled={disabled} variant={variant} />;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,104 @@
|
|||||||
|
import {
|
||||||
|
FONT_TOOLBAR_BUTTON,
|
||||||
|
SHORTCODE_TOOLBAR_BUTTON,
|
||||||
|
BLOCKQUOTE_TOOLBAR_BUTTON,
|
||||||
|
BOLD_TOOLBAR_BUTTON,
|
||||||
|
CODE_BLOCK_TOOLBAR_BUTTON,
|
||||||
|
CODE_TOOLBAR_BUTTON,
|
||||||
|
DECREASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
DELETE_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
DELETE_ROW_TOOLBAR_BUTTON,
|
||||||
|
DELETE_TABLE_TOOLBAR_BUTTON,
|
||||||
|
INCRASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
INSERT_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
IMAGE_TOOLBAR_BUTTON,
|
||||||
|
FILE_LINK_TOOLBAR_BUTTON,
|
||||||
|
INSERT_ROW_TOOLBAR_BUTTON,
|
||||||
|
INSERT_TABLE_TOOLBAR_BUTTON,
|
||||||
|
ITALIC_TOOLBAR_BUTTON,
|
||||||
|
ORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
STRIKETHROUGH_TOOLBAR_BUTTON,
|
||||||
|
UNORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
} from '@staticcms/core/constants/toolbar_buttons';
|
||||||
|
|
||||||
|
const LowLevelButtonsArrayField = {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
enum: [
|
||||||
|
BLOCKQUOTE_TOOLBAR_BUTTON,
|
||||||
|
BOLD_TOOLBAR_BUTTON,
|
||||||
|
CODE_BLOCK_TOOLBAR_BUTTON,
|
||||||
|
CODE_TOOLBAR_BUTTON,
|
||||||
|
DECREASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
DELETE_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
DELETE_ROW_TOOLBAR_BUTTON,
|
||||||
|
DELETE_TABLE_TOOLBAR_BUTTON,
|
||||||
|
INCRASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
INSERT_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
IMAGE_TOOLBAR_BUTTON,
|
||||||
|
FILE_LINK_TOOLBAR_BUTTON,
|
||||||
|
INSERT_ROW_TOOLBAR_BUTTON,
|
||||||
|
INSERT_TABLE_TOOLBAR_BUTTON,
|
||||||
|
ITALIC_TOOLBAR_BUTTON,
|
||||||
|
ORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
STRIKETHROUGH_TOOLBAR_BUTTON,
|
||||||
|
UNORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const MarkdownToolbarItemField = {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
anyOf: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
enum: [
|
||||||
|
FONT_TOOLBAR_BUTTON,
|
||||||
|
SHORTCODE_TOOLBAR_BUTTON,
|
||||||
|
BLOCKQUOTE_TOOLBAR_BUTTON,
|
||||||
|
BOLD_TOOLBAR_BUTTON,
|
||||||
|
CODE_BLOCK_TOOLBAR_BUTTON,
|
||||||
|
CODE_TOOLBAR_BUTTON,
|
||||||
|
DECREASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
DELETE_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
DELETE_ROW_TOOLBAR_BUTTON,
|
||||||
|
DELETE_TABLE_TOOLBAR_BUTTON,
|
||||||
|
INCRASE_IDENT_TOOLBAR_BUTTON,
|
||||||
|
INSERT_COLUMN_TOOLBAR_BUTTON,
|
||||||
|
IMAGE_TOOLBAR_BUTTON,
|
||||||
|
FILE_LINK_TOOLBAR_BUTTON,
|
||||||
|
INSERT_ROW_TOOLBAR_BUTTON,
|
||||||
|
INSERT_TABLE_TOOLBAR_BUTTON,
|
||||||
|
ITALIC_TOOLBAR_BUTTON,
|
||||||
|
ORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
STRIKETHROUGH_TOOLBAR_BUTTON,
|
||||||
|
UNORDERED_LIST_TOOLBAR_BUTTON,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
label: { type: 'string' },
|
||||||
|
icon: { type: 'string' },
|
||||||
|
groups: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
items: LowLevelButtonsArrayField,
|
||||||
|
},
|
||||||
|
required: ['items'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['label', 'groups'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
properties: {
|
properties: {
|
||||||
default: { type: 'string' },
|
default: { type: 'string' },
|
||||||
@ -5,6 +106,16 @@ export default {
|
|||||||
public_folder: { type: 'string' },
|
public_folder: { type: 'string' },
|
||||||
choose_url: { type: 'boolean' },
|
choose_url: { type: 'boolean' },
|
||||||
multiple: { type: 'boolean' },
|
multiple: { type: 'boolean' },
|
||||||
|
toolbar_buttons: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
main: MarkdownToolbarItemField,
|
||||||
|
empty: MarkdownToolbarItemField,
|
||||||
|
selection: MarkdownToolbarItemField,
|
||||||
|
table_empty: MarkdownToolbarItemField,
|
||||||
|
table_select: MarkdownToolbarItemField,
|
||||||
|
},
|
||||||
|
},
|
||||||
media_library: {
|
media_library: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -16,13 +16,14 @@ _Please note:_ If you want to use your markdown editor to fill a markdown file c
|
|||||||
|
|
||||||
For common options, see [Common widget options](/docs/widgets#common-widget-options).
|
For common options, see [Common widget options](/docs/widgets#common-widget-options).
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| ------------- | --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| --------------- | --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| default | string | `''` | _Optional_. The default value for the field. Accepts markdown content |
|
| default | string | `''` | _Optional_. The default value for the field. Accepts markdown content |
|
||||||
| media_folder | string | | _Optional_. Specifies the folder path where uploaded files should be saved, relative to the base of the repo |
|
| media_folder | string | | _Optional_. Specifies the folder path where uploaded files should be saved, relative to the base of the repo |
|
||||||
| public_folder | string | | _Optional_. Specifies the folder path where the files uploaded by the media library will be accessed, relative to the base of the built site |
|
| public_folder | string | | _Optional_. Specifies the folder path where the files uploaded by the media library will be accessed, relative to the base of the built site |
|
||||||
| media_library | Media Library Options | `{}` | _Optional_. Media library settings to apply when the media library is opened by the current widget. See [Media Library](/docs/configuration-options#media-library) |
|
| media_library | Media Library Options | `{}` | _Optional_. Media library settings to apply when the media library is opened by the current widget. See [Media Library](/docs/configuration-options#media-library) |
|
||||||
| choose_url | boolean | `true` | _Optional_. When set to `false`, the "Insert from URL" button will be hidden |
|
| choose_url | boolean | `true` | _Optional_. When set to `false`, the "Insert from URL" button will be hidden |
|
||||||
|
| toolbar_buttons | object | [] | _Optional_. Specifies which toolbar items to show for the markdown widget. See [Toolbar Customization](#toolbar-customization) |
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@ -47,6 +48,234 @@ This would render as:
|
|||||||
|
|
||||||
_Please note:_ The markdown widget outputs a raw markdown string. Your static site generator may or may not render the markdown to HTML automatically. Consult with your static site generator's documentation for more information about rendering markdown.
|
_Please note:_ The markdown widget outputs a raw markdown string. Your static site generator may or may not render the markdown to HTML automatically. Consult with your static site generator's documentation for more information about rendering markdown.
|
||||||
|
|
||||||
|
## Toolbar Customization
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --------------- | ------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||||
|
| main | object | See [Main Toolbar](#main-toolbar) | _Optional_. A list of buttons and menus for the top level toolbar |
|
||||||
|
| empty | object | See [Empty Toolbar](#empty-toolbar) | _Optional_. A list of buttons and menus for the popup toolbar when in an empty paragraph |
|
||||||
|
| selection | object | See [Selection Toolbar](#selection-toolbar) | _Optional_. A list of buttons and menus for the popup toolbar when selecting text outside of a table cell |
|
||||||
|
| table_empty | object | See [Empty Table Cell Toolbar](#empty-table-cell-toolbar) | _Optional_. A list of buttons and menus for the popup toolbar when in an empty table cell |
|
||||||
|
| table_selection | object | See [Table Selection Toolbar](#table-selection-toolbar) | _Optional_. A list of buttons and menus for the popup toolbar when selecting text in a table cell |
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
All toolbars can be customized with a list consisting of buttons and dropdown menus.
|
||||||
|
|
||||||
|
#### Buttons
|
||||||
|
|
||||||
|
Buttons can be configured simply with their name. The following options are available:
|
||||||
|
|
||||||
|
- `blockquote`
|
||||||
|
- `bold`
|
||||||
|
- `code`
|
||||||
|
- `code-block`
|
||||||
|
- `decrease-indent`
|
||||||
|
- `delete-column`
|
||||||
|
- `delete-row`
|
||||||
|
- `delete-table`
|
||||||
|
- `file-link`
|
||||||
|
- `font`
|
||||||
|
- `image`
|
||||||
|
- `increase-indent`
|
||||||
|
- `insert-column`
|
||||||
|
- `insert-row`
|
||||||
|
- `insert-table`
|
||||||
|
- `italic`
|
||||||
|
- `ordered-list`
|
||||||
|
- `shortcode`
|
||||||
|
- `strikethrough`
|
||||||
|
- `unordered-list`
|
||||||
|
|
||||||
|
#### Menus
|
||||||
|
|
||||||
|
The following options are available for menus:
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ------ | -------------- | ----------------------------------------------------------------------------------------- |
|
||||||
|
| label | string | The name and tooltip label for the menu |
|
||||||
|
| icon | string | _Optional_. The icon to use for the menu. If not supplied a default add icon will be used |
|
||||||
|
| groups | list of groups | A list groups of menu items. Each group is separated by a divider |
|
||||||
|
|
||||||
|
##### Menu Groups
|
||||||
|
|
||||||
|
The following options are available for menu groups:
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ----- | --------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| items | list of strings | The name of the toolbar buttons in the group. All [buttons](#buttons) are available in menus except `font` and `shortcode` |
|
||||||
|
|
||||||
|
### Default Values
|
||||||
|
|
||||||
|
Below are the default values for the various toolbars:
|
||||||
|
|
||||||
|
#### Main Toolbar
|
||||||
|
|
||||||
|
<CodeTabs>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
main:
|
||||||
|
- bold
|
||||||
|
- italic
|
||||||
|
- strikethrough
|
||||||
|
- code
|
||||||
|
- font
|
||||||
|
- unordered-list
|
||||||
|
- ordered-list
|
||||||
|
- decrease-indent
|
||||||
|
- increase-indent
|
||||||
|
- shortcode
|
||||||
|
- label: Insert
|
||||||
|
groups:
|
||||||
|
- items:
|
||||||
|
- blockquote
|
||||||
|
- code-block
|
||||||
|
- items:
|
||||||
|
- insert-table
|
||||||
|
- items:
|
||||||
|
- image
|
||||||
|
- file-link
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
main: [
|
||||||
|
'bold',
|
||||||
|
'italic',
|
||||||
|
'strikethrough',
|
||||||
|
'code',
|
||||||
|
'font',
|
||||||
|
'unordered-list',
|
||||||
|
'ordered-list',
|
||||||
|
'decrease-indent',
|
||||||
|
'increase-indent',
|
||||||
|
'shortcode',
|
||||||
|
{
|
||||||
|
label: 'Insert',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
items: ['blockquote', 'code-block'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: ['insert-table'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: ['image', 'file-link'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeTabs>
|
||||||
|
|
||||||
|
#### Empty Toolbar
|
||||||
|
|
||||||
|
<CodeTabs>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
empty: []
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
empty: [];
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeTabs>
|
||||||
|
|
||||||
|
#### Selection Toolbar
|
||||||
|
|
||||||
|
<CodeTabs>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
selection:
|
||||||
|
- bold
|
||||||
|
- italic
|
||||||
|
- strikethrough
|
||||||
|
- code
|
||||||
|
- font
|
||||||
|
- file-link
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
selection: ['bold', 'italic', 'strikethrough', 'code', 'font', 'file-link'];
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeTabs>
|
||||||
|
|
||||||
|
#### Empty Table Cell Toolbar
|
||||||
|
|
||||||
|
<CodeTabs>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
table_empty:
|
||||||
|
- bold
|
||||||
|
- italic
|
||||||
|
- strikethrough
|
||||||
|
- code
|
||||||
|
- insert-row
|
||||||
|
- delete-row
|
||||||
|
- insert-column
|
||||||
|
- delete-column
|
||||||
|
- delete-table
|
||||||
|
- file-link
|
||||||
|
- image
|
||||||
|
- shortcode
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
table_empty: [
|
||||||
|
'bold',
|
||||||
|
'italic',
|
||||||
|
'strikethrough',
|
||||||
|
'code',
|
||||||
|
'insert-row',
|
||||||
|
'delete-row',
|
||||||
|
'insert-column',
|
||||||
|
'delete-column',
|
||||||
|
'delete-table',
|
||||||
|
'file-link',
|
||||||
|
'image',
|
||||||
|
'shortcode',
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeTabs>
|
||||||
|
|
||||||
|
#### Table Selection Toolbar
|
||||||
|
|
||||||
|
<CodeTabs>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
table_selection:
|
||||||
|
- bold
|
||||||
|
- italic
|
||||||
|
- strikethrough
|
||||||
|
- code
|
||||||
|
- insert-row
|
||||||
|
- delete-row
|
||||||
|
- insert-column
|
||||||
|
- delete-column
|
||||||
|
- delete-table
|
||||||
|
- file-link
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
table_selection: [
|
||||||
|
'bold',
|
||||||
|
'italic',
|
||||||
|
'strikethrough',
|
||||||
|
'code',
|
||||||
|
'insert-row',
|
||||||
|
'delete-row',
|
||||||
|
'insert-column',
|
||||||
|
'delete-column',
|
||||||
|
'delete-table',
|
||||||
|
'file-link',
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeTabs>
|
||||||
|
|
||||||
## Shortcodes
|
## Shortcodes
|
||||||
|
|
||||||
Shortcodes can be added to customize the Markdown editor via `registerShortcode`.
|
Shortcodes can be added to customize the Markdown editor via `registerShortcode`.
|
||||||
|
@ -36,6 +36,7 @@ const Anchor = ({ href = '', children = '' }: AnchorProps) => {
|
|||||||
document.querySelector(href)?.scrollIntoView({
|
document.querySelector(href)?.scrollIntoView({
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
|
history.pushState(null, '', href);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -39,6 +39,7 @@ const Header3 = ({ variant, children = '' }: Header3Props) => {
|
|||||||
const hasText = useMemo(() => isNotEmpty(textContent), [textContent]);
|
const hasText = useMemo(() => isNotEmpty(textContent), [textContent]);
|
||||||
return (
|
return (
|
||||||
<Typography
|
<Typography
|
||||||
|
id={anchor}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
component={variant}
|
component={variant}
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -8,7 +8,7 @@ const StyledList = styled('ul')(
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
${theme.breakpoints.down('lg')} {
|
${theme.breakpoints.down('lg')} {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
@ -64,6 +64,7 @@ const DocsHeadings = ({ headings, activeId }: DocsHeadingsProps) => (
|
|||||||
document.querySelector(`#${heading.id}`)?.scrollIntoView({
|
document.querySelector(`#${heading.id}`)?.scrollIntoView({
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
|
history.pushState(null, '', `#${heading.id}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{heading.title}
|
{heading.title}
|
||||||
@ -79,6 +80,7 @@ const DocsHeadings = ({ headings, activeId }: DocsHeadingsProps) => (
|
|||||||
document.querySelector(`#${child.id}`)?.scrollIntoView({
|
document.querySelector(`#${child.id}`)?.scrollIntoView({
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
|
history.pushState(null, '', `#${child.id}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{child.title}
|
{child.title}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user