Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZUIEditor tools: Ordered list, bullet list, bold and italic #2479

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,16 @@ const EmailPage: PageWithLayout = () => {
<title>hejj</title>
</Head>
<Box>
<ZUIEditor enableButton enableHeading enableImage enableVariable />
<ZUIEditor
enableBold
enableButton
enableHeading
enableImage
enableItalic
enableLink
enableLists
enableVariable
/>
</Box>
</>
);
Expand Down
77 changes: 16 additions & 61 deletions src/zui/ZUIEditor/EditorOverlays/BlockToolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,34 @@
import { Box, Button, Paper } from '@mui/material';
import { useActive, useCommands, useEditorState } from '@remirror/react';
import { FC, useEffect, useState } from 'react';
import { useCommands } from '@remirror/react';
import { FC } from 'react';

import { NodeWithPosition } from '../LinkExtensionUI';
import VariableToolButton from './VariableToolButton';
import { VariableName } from '../extensions/VariableExtension';
import BoldToolButton from './BoldToolButton';
import ItalicToolButton from './ItalicToolButton';
import LinkToolButton from './LinkToolButton';

type BlockToolbarProps = {
curBlockType: string;
curBlockY: number;
enableBold: boolean;
enableItalic: boolean;
enableLink: boolean;
enableVariable: boolean;
pos: number;
};

const BlockToolbar: FC<BlockToolbarProps> = ({
curBlockType,
curBlockY,
enableBold,
enableItalic,
enableLink,
enableVariable,
pos,
}) => {
const active = useActive();
const state = useEditorState();
const {
convertParagraph,
focus,
insertEmptyLink,
insertVariable,
toggleHeading,
pickImage,
removeLink,
removeAllLinksInRange,
setLink,
} = useCommands();

const [selectedNodes, setSelectedNodes] = useState<NodeWithPosition[]>([]);

useEffect(() => {
const linkNodes: NodeWithPosition[] = [];
state.doc.nodesBetween(
state.selection.from,
state.selection.to,
(node, index) => {
if (node.isText) {
if (node.marks.some((mark) => mark.type.name == 'zlink')) {
linkNodes.push({ from: index, node, to: index + node.nodeSize });
}
}
}
);
setSelectedNodes(linkNodes);
}, [state.selection]);
const { convertParagraph, focus, insertVariable, toggleHeading, pickImage } =
useCommands();

return (
<Box position="relative">
Expand Down Expand Up @@ -84,33 +63,9 @@ const BlockToolbar: FC<BlockToolbarProps> = ({
<Button onClick={() => toggleHeading()}>
Convert to heading
</Button>
<Button
onClick={() => {
if (!active.zlink()) {
if (state.selection.empty) {
insertEmptyLink();
focus();
} else {
setLink();
focus();
}
} else {
if (selectedNodes.length == 1) {
removeLink({
from: selectedNodes[0].from,
to: selectedNodes[0].to,
});
} else if (selectedNodes.length > 1) {
removeAllLinksInRange({
from: state.selection.from,
to: state.selection.to,
});
}
}
}}
>
Link
</Button>
{enableLink && <LinkToolButton />}
{enableBold && <BoldToolButton />}
{enableItalic && <ItalicToolButton />}
</>
)}
{enableVariable &&
Expand Down
23 changes: 23 additions & 0 deletions src/zui/ZUIEditor/EditorOverlays/BoldToolButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { FormatBold } from '@mui/icons-material';
import { IconButton } from '@mui/material';
import { useActive, useCommands } from '@remirror/react';
import { FC } from 'react';

const BoldToolButton: FC = () => {
const active = useActive();
const { focus, toggleBold } = useCommands();

return (
<IconButton
color={active.bold() ? 'primary' : 'secondary'}
onClick={() => {
toggleBold();
focus();
}}
>
<FormatBold />
</IconButton>
);
};

export default BoldToolButton;
23 changes: 23 additions & 0 deletions src/zui/ZUIEditor/EditorOverlays/ItalicToolButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { FormatItalic } from '@mui/icons-material';
import { IconButton } from '@mui/material';
import { useActive, useCommands } from '@remirror/react';
import { FC } from 'react';

const ItalicToolButton: FC = () => {
const active = useActive();
const { focus, toggleItalic } = useCommands();

return (
<IconButton
color={active.italic() ? 'primary' : 'secondary'}
onClick={() => {
toggleItalic();
focus();
}}
>
<FormatItalic />
</IconButton>
);
};

export default ItalicToolButton;
63 changes: 63 additions & 0 deletions src/zui/ZUIEditor/EditorOverlays/LinkToolButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { IconButton } from '@mui/material';
import { useActive, useCommands, useEditorState } from '@remirror/react';
import { FC, useEffect, useState } from 'react';
import { InsertLink, LinkOff } from '@mui/icons-material';

import { NodeWithPosition } from '../LinkExtensionUI';

const LinkToolButton: FC = () => {
const active = useActive();
const state = useEditorState();
const { focus, insertEmptyLink, removeAllLinksInRange, removeLink, setLink } =
useCommands();

const [selectedNodes, setSelectedNodes] = useState<NodeWithPosition[]>([]);

useEffect(() => {
const linkNodes: NodeWithPosition[] = [];
state.doc.nodesBetween(
state.selection.from,
state.selection.to,
(node, index) => {
if (node.isText) {
if (node.marks.some((mark) => mark.type.name == 'zlink')) {
linkNodes.push({ from: index, node, to: index + node.nodeSize });
}
}
}
);
setSelectedNodes(linkNodes);
}, [state.selection]);

return (
<IconButton
onClick={() => {
if (!active.zlink()) {
if (state.selection.empty) {
insertEmptyLink();
focus();
} else {
setLink();
focus();
}
} else {
if (selectedNodes.length == 1) {
removeLink({
from: selectedNodes[0].from,
to: selectedNodes[0].to,
});
} else if (selectedNodes.length > 1) {
removeAllLinksInRange({
from: state.selection.from,
to: state.selection.to,
});
}
}
}}
>
{active.zlink() ? <LinkOff /> : <InsertLink />}
</IconButton>
);
};

export default LinkToolButton;
14 changes: 13 additions & 1 deletion src/zui/ZUIEditor/EditorOverlays/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,19 @@ type Props = {
id: string;
label: string;
}[];
enableBold: boolean;
enableItalic: boolean;
enableLink: boolean;
enableVariable: boolean;
};

const EditorOverlays: FC<Props> = ({ blocks, enableVariable }) => {
const EditorOverlays: FC<Props> = ({
blocks,
enableBold,
enableItalic,
enableLink,
enableVariable,
}) => {
const view = useEditorView();
const state = useEditorState();
const positioner = usePositioner('cursor');
Expand Down Expand Up @@ -145,6 +154,9 @@ const EditorOverlays: FC<Props> = ({ blocks, enableVariable }) => {
<BlockToolbar
curBlockType={currentBlock.type}
curBlockY={currentBlock.rect.y}
enableBold={enableBold}
enableItalic={enableItalic}
enableLink={enableLink}
enableVariable={enableVariable}
pos={state.selection.$anchor.pos}
/>
Expand Down
47 changes: 16 additions & 31 deletions src/zui/ZUIEditor/extensions/BlockMenuExtension.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { Node } from '@remirror/pm/model';
import { TextSelection } from '@remirror/pm/state';
import { Suggester } from '@remirror/pm/suggest';
import {
legacyCommand as command,
CommandFunction,
extension,
getActiveNode,
Handler,
PlainExtension,
} from 'remirror';

type BlockMenuOptions = {
blockFactories: Record<string, () => Node>;
blockFactories: Record<string, CommandFunction>;
onBlockQuery?: Handler<(query: string | null) => void>;
};

Expand All @@ -26,11 +23,17 @@ class BlockMenuExtension extends PlainExtension<BlockMenuOptions> {
return [
{
char: '/',
isValidPosition: (range) => range.$from.parentOffset == 0,
isValidPosition: (range) => {
return range.$from.parentOffset == 0;
},
name: 'slash',
onChange: (details) => {
onChange: (details, tr) => {
const resolved = tr.doc.resolve(details.range.to);
const exited = !!details.exitReason;
this.options.onBlockQuery(exited ? null : details.query.full);
const inParagraph = resolved.node(1).type.name == 'paragraph';
this.options.onBlockQuery(
exited || !inParagraph ? null : details.query.full
);
},
},
];
Expand All @@ -40,31 +43,13 @@ class BlockMenuExtension extends PlainExtension<BlockMenuOptions> {
//@ts-ignore
@command()
insertBlock(type: string): CommandFunction {
return ({ dispatch, state, tr }) => {
const oldNode = getActiveNode({
state,
type: 'paragraph',
});
if (oldNode) {
const factory = this.options.blockFactories[type];
if (factory) {
const newNode = factory();
if (dispatch && newNode) {
tr = tr.replaceWith(oldNode.pos, oldNode.end, newNode);
tr = tr.setSelection(
TextSelection.create(
tr.doc,
oldNode.pos + 1,
oldNode.pos + newNode.nodeSize - 1
)
);
dispatch(tr);
}
return (props) => {
const { state, tr } = props;
const resolved = state.doc.resolve(state.selection.$head.pos);
tr.deleteRange(resolved.start(), resolved.end());

return true;
}
}
return false;
const factoryCommand = this.options.blockFactories[type];
return factoryCommand(props);
};
}

Expand Down
Loading
Loading