Skip to content

Commit

Permalink
v4.8.0 (#291)
Browse files Browse the repository at this point in the history
* Table plugin (#272)
* Multi selection
* Divider plugin
* Markdown deseralize/serialize fixes
* Markdown deseralize/serialize fixes
* Commands API
* Events API: `onBeforeCreate`, `onCreate`, `onDestroy`
* Extensions for slate editor
* Landing is updated
  • Loading branch information
Darginec05 authored Sep 22, 2024
1 parent 7eb2d7f commit 9651c02
Show file tree
Hide file tree
Showing 230 changed files with 9,012 additions and 3,690 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ All of this is customizable, extensible, and easy to set up!
- Plugins

- [**@yoopta/paragraph**](https://github.com/Darginec05/Yoopta-Editor/blob/master/packages/plugins/paragraph/README.md)
- [**@yoopta/accordion**](https://github.com/Darginec05/Yoopta-Editor/blob/master/packages/plugins/accordion/README.md)
- [**@yoopta/blockquote**](https://github.com/Darginec05/Yoopta-Editor/blob/master/packages/plugins/blockquote/README.md)
- [**@yoopta/accordion**](https://github.com/Darginec05/Yoopta-Editor/blob/master/packages/plugins/accordion/README.md)
- [**@yoopta/divider**](https://github.com/Darginec05/Yoopta-Editor/blob/master/packages/plugins/divider/README.md)
- [**@yoopta/table**](https://github.com/Darginec05/Yoopta-Editor/blob/master/packages/plugins/table/README.md)
- [**@yoopta/code**](https://github.com/Darginec05/Yoopta-Editor/blob/master/packages/plugins/code/README.md)
- [**@yoopta/embed**](https://github.com/Darginec05/Yoopta-Editor/blob/master/packages/plugins/embed/README.md)
- [**@yoopta/image**](https://github.com/Darginec05/Yoopta-Editor/blob/master/packages/plugins/image/README.md)
Expand Down Expand Up @@ -149,6 +151,8 @@ Here is list of available plugins

- @yoopta/paragraph
- @yoopta/blockquote
- @yoopta/table
- @yoopta/divider
- @yoopta/accordion
- @yoopta/code
- @yoopta/embed
Expand Down
4 changes: 2 additions & 2 deletions packages/core/editor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@yoopta/editor",
"version": "4.7.0",
"version": "4.8.0",
"license": "MIT",
"private": false,
"main": "dist/index.js",
Expand Down Expand Up @@ -67,5 +67,5 @@
"url": "https://github.com/Darginec05/Yoopta-Editor/issues"
},
"homepage": "https://github.com/Darginec05/Yoopta-Editor#readme",
"gitHead": "600e0cf267a0ce7df074ddc7db1114d67c4185d1"
"gitHead": "12d8460dfe0ead89ee1d6f3f2f1fc68239e93d4c"
}
89 changes: 47 additions & 42 deletions packages/core/editor/src/UI/BlockOptions/BlockOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,18 @@ const BlockOptionsSeparator = ({ className = '' }: BlockOptionsSeparatorProps) =
<div className={`yoopta-block-options-separator ${className}`} />
);

type BlockOptionsProps = {
export type BlockOptionsProps = {
isOpen: boolean;
onClose: () => void;
refs: any;
style: CSSProperties;
children?: React.ReactNode;
actions?: ['delete', 'duplicate', 'turnInto', 'copy'] | null;
};

const BlockOptions = ({ isOpen, onClose, refs, style, children }: BlockOptionsProps) => {
const DEFAULT_ACTIONS: BlockOptionsProps['actions'] = ['delete', 'duplicate', 'turnInto', 'copy'];

const BlockOptions = ({ isOpen, onClose, refs, style, actions = DEFAULT_ACTIONS, children }: BlockOptionsProps) => {
const editor = useYooptaEditor();
const tools = useYooptaTools();
const [isActionMenuOpen, setIsActionMenuOpen] = useState(false);
Expand Down Expand Up @@ -106,50 +109,52 @@ const BlockOptions = ({ isOpen, onClose, refs, style, children }: BlockOptionsPr
// [TODO] - take care about SSR
<Portal id="yoo-block-options-portal">
<Overlay lockScroll className="yoo-editor-z-[100]" onClick={onClose}>
<div style={style} ref={refs.setFloating}>
<div style={style} ref={refs.setFloating} contentEditable={false}>
<BlockOptionsMenuContent>
<BlockOptionsMenuGroup>
<BlockOptionsMenuItem>
<button type="button" className="yoopta-block-options-button" onClick={onDelete}>
<TrashIcon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Delete
</button>
</BlockOptionsMenuItem>
<BlockOptionsMenuItem>
<button type="button" className="yoopta-block-options-button" onClick={onDuplicate}>
<CopyIcon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Duplicate
</button>
</BlockOptionsMenuItem>
{!!ActionMenu && !isVoidElement && !editor.blocks[currentBlock?.type || '']?.hasCustomEditor && (
{actions !== null && (
<BlockOptionsMenuGroup>
<BlockOptionsMenuItem>
<button type="button" className="yoopta-block-options-button" onClick={onDelete}>
<TrashIcon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Delete
</button>
</BlockOptionsMenuItem>
<BlockOptionsMenuItem>
<button type="button" className="yoopta-block-options-button" onClick={onDuplicate}>
<CopyIcon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Duplicate
</button>
</BlockOptionsMenuItem>
{!!ActionMenu && !isVoidElement && !editor.blocks[currentBlock?.type || '']?.hasCustomEditor && (
<BlockOptionsMenuItem>
{isActionMenuMounted && (
<Portal id="yoo-block-options-portal">
<Overlay lockScroll className="yoo-editor-z-[100]" onClick={() => setIsActionMenuOpen(false)}>
<div style={actionMenuStyles} ref={actionMenuRefs.setFloating}>
<ActionMenu {...actionMenuRenderProps} />
</div>
</Overlay>
</Portal>
)}
<button
type="button"
className="yoopta-block-options-button"
ref={actionMenuRefs.setReference}
onClick={() => setIsActionMenuOpen((open) => !open)}
>
<TurnIcon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Turn into
</button>
</BlockOptionsMenuItem>
)}
<BlockOptionsMenuItem>
{isActionMenuMounted && (
<Portal id="yoo-block-options-portal">
<Overlay lockScroll className="yoo-editor-z-[100]" onClick={() => setIsActionMenuOpen(false)}>
<div style={actionMenuStyles} ref={actionMenuRefs.setFloating}>
<ActionMenu {...actionMenuRenderProps} />
</div>
</Overlay>
</Portal>
)}
<button
type="button"
className="yoopta-block-options-button"
ref={actionMenuRefs.setReference}
onClick={() => setIsActionMenuOpen((open) => !open)}
>
<TurnIcon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Turn into
<button type="button" className="yoopta-block-options-button" onClick={onCopy}>
<Link2Icon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Copy link to block
</button>
</BlockOptionsMenuItem>
)}
<BlockOptionsMenuItem>
<button type="button" className="yoopta-block-options-button" onClick={onCopy}>
<Link2Icon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Copy link to block
</button>
</BlockOptionsMenuItem>
</BlockOptionsMenuGroup>
</BlockOptionsMenuGroup>
)}
{children}
</BlockOptionsMenuContent>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const ExtendedBlockActions = ({ id, className, style, onClick, children }: Props
)}
<button
type="button"
contentEditable={false}
ref={blockOptionRefs.setReference}
id={id}
className={`yoopta-button yoopta-extended-block-actions ${className || ''}`}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/editor/src/UI/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ExtendedBlockActions } from './ExtendedBlockActions/ExtendedBlockAction
import { Portal } from './Portal/Portal';
import { Overlay } from './Overlay/Overlay';

export { type BlockOptionsProps } from './BlockOptions/BlockOptions';

export const UI = {
...BlockOptionsUI,
ExtendedBlockActions,
Expand Down
83 changes: 22 additions & 61 deletions packages/core/editor/src/YooptaEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { YooptaContextProvider } from './contexts/YooptaContext/YooptaContext';
import { getDefaultYooptaChildren } from './components/Editor/utils';
import { Editor } from './components/Editor/Editor';
import { CSSProperties, useMemo, useState } from 'react';
import { YooEditor, YooptaBlockData, YooptaContentValue } from './editor/types';
import { Plugin, PluginElementProps } from './plugins/types';
import NoSSR from './components/NoSsr/NoSsr';
import { SlateElement, YooEditor, YooptaBlockData, YooptaContentValue } from './editor/types';
import { Plugin } from './plugins/types';
import { Tools, ToolsProvider } from './contexts/YooptaContext/ToolsContext';
import {
buildBlocks,
buildBlockShortcuts,
buildBlockSlateEditors,
buildCommands,
buildMarks,
buildPlugins,
} from './utils/editorBuilders';
Expand All @@ -22,7 +22,7 @@ import { generateId } from './utils/generateId';
type Props = {
id?: string;
editor: YooEditor;
plugins: YooptaPlugin<string, PluginElementProps<any>, Record<string, unknown>>[];
plugins: Readonly<YooptaPlugin<Record<string, SlateElement>>[]>;
marks?: YooptaMark<any>[];
value?: YooptaContentValue;
autoFocus?: boolean;
Expand Down Expand Up @@ -54,18 +54,6 @@ function validateInitialValue(value: any): boolean {
return true;
}

const isLegacyVersionInUse = (value: any): boolean => {
if (Array.isArray(value) && value.length > 0) {
return value.some((node) => {
if (node.id || node.nodeType || node.type || node.children) {
return true;
}
});
}

return false;
};

const YooptaEditor = ({
id,
editor,
Expand All @@ -92,10 +80,10 @@ const YooptaEditor = ({
}, [marksProps]);

const plugins = useMemo(() => {
return pluginsProps.map((plugin) => plugin.getPlugin as Plugin<string, any, any>);
return pluginsProps.map((plugin) => plugin.getPlugin as Plugin<Record<string, SlateElement>>);
}, [pluginsProps]);

const [editorState, setEditorState] = useState<{ editor: YooEditor<any>; version: number }>(() => {
const [editorState, setEditorState] = useState<{ editor: YooEditor; version: number }>(() => {
if (!editor.id) editor.id = id || generateId();
editor.applyChanges = applyChanges;
editor.readOnly = readOnly || false;
Expand All @@ -114,6 +102,7 @@ const YooptaEditor = ({
editor.blockEditorsMap = buildBlockSlateEditors(editor);
editor.shortcuts = buildBlockShortcuts(editor);
editor.plugins = buildPlugins(plugins);
editor.commands = buildCommands(editor, plugins);

editor.on = Events.on;
editor.once = Events.once;
Expand All @@ -123,50 +112,22 @@ const YooptaEditor = ({
return { editor, version: 0 };
});

if (isLegacyVersionInUse(value)) {
console.error('Legacy version of Yoopta-Editor in use');

return (
<div>
<h1>Legacy version of the Yoopta-Editor is used</h1>
<p>It looks like you are using a legacy version of the editor.</p>
<p>
The structure of value has changed in new <b>@v4</b> version
</p>
<p>
{/* [TODO] - add link to migration guide */}
Please, check the migration guide to update your editor to the new <b>@v4</b> version.
<a href="" />
</p>
<p>
If you have specific case please{' '}
<a href="https://github.com/Darginec05/Yoopta-Editor/issues" target="_blank" rel="noopener noreferrer">
open the issue
</a>{' '}
and we will solve your problem with migration
</p>
</div>
);
}

return (
<NoSSR>
<YooptaContextProvider editorState={editorState}>
<ToolsProvider tools={tools}>
<Editor
placeholder={placeholder}
marks={marks}
autoFocus={autoFocus}
className={className}
selectionBoxRoot={selectionBoxRoot}
width={width}
style={style}
>
{children}
</Editor>
</ToolsProvider>
</YooptaContextProvider>
</NoSSR>
<YooptaContextProvider editorState={editorState}>
<ToolsProvider tools={tools}>
<Editor
placeholder={placeholder}
marks={marks}
autoFocus={autoFocus}
className={className}
selectionBoxRoot={selectionBoxRoot}
width={width}
style={style}
>
{children}
</Editor>
</ToolsProvider>
</YooptaContextProvider>
);
};

Expand Down
49 changes: 22 additions & 27 deletions packages/core/editor/src/components/Block/Block.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,49 @@
import React, { useCallback, useMemo } from 'react';
import { useYooptaEditor } from '../../contexts/YooptaContext/YooptaContext';
import { useSortable } from '@dnd-kit/sortable';
import { CSSProperties, useState } from 'react';
import { BlockActions } from './BlockActions';
import { YooptaBlockData } from '../../editor/types';
import { useBlockStyles } from './hooks';

const Block = ({ children, block, blockId }) => {
type BlockProps = {
children: React.ReactNode;
block: YooptaBlockData;
blockId: string;
};

const Block = ({ children, block, blockId }: BlockProps) => {
const editor = useYooptaEditor();
const [activeBlockId, setActiveBlockId] = React.useState<string | null>(null);

const [activeBlockId, setActiveBlockId] = useState<string | null>(null);
const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isOver, isDragging } =
useSortable({ id: blockId, disabled: editor.readOnly });
const styles = useBlockStyles(block, transform, transition, isDragging, isOver);

const align = block.meta.align || 'left';
const className = `yoopta-block yoopta-align-${align}`;

const style: CSSProperties = {
// [TODO] = handle max depth
marginLeft: `${block.meta.depth * 20}px`,
transform: transform ? `translate3d(${transform.x}px, ${transform.y}px, 0)` : 'none',
transition,
opacity: isDragging ? 0.7 : 1,
};

const isSelected = editor.selectedBlocks?.includes(block.meta.order);
const isHovered = activeBlockId === blockId;

const onChangeActiveBlock = (id: string) => setActiveBlockId(id);

const handleMouseEnter = () => {
const handleMouseEnter = useCallback(() => {
if (editor.readOnly) return;
setActiveBlockId(blockId);
};
const handleMouseLeave = () => {
}, [editor.readOnly, blockId]);

const handleMouseLeave = useCallback(() => {
if (editor.readOnly) return;
setActiveBlockId(null);
};
}, [editor.readOnly]);

const contentStyles = { borderBottom: isOver && !isDragging ? '2px solid #007aff' : 'none' };
const dragHandleProps = useMemo(() => ({ setActivatorNodeRef, attributes, listeners }), [block]);

return (
<div
ref={setNodeRef}
className={className}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={style}
style={styles.container}
data-hovered-block={isHovered}
data-yoopta-block
data-yoopta-block-id={blockId}
Expand All @@ -53,17 +53,12 @@ const Block = ({ children, block, blockId }) => {
<BlockActions
block={block}
editor={editor}
dragHandleProps={{ setActivatorNodeRef, attributes, listeners }}
dragHandleProps={dragHandleProps}
showActions={isHovered}
onChangeActiveBlock={onChangeActiveBlock}
onChangeActiveBlock={setActiveBlockId}
/>
)}
<div
// [TODO] - check in which direction is dragging
style={contentStyles}
>
{children}
</div>
<div style={styles.content}>{children}</div>
{isSelected && !editor.readOnly && <div className="yoopta-selection-block" />}
</div>
);
Expand Down
Loading

0 comments on commit 9651c02

Please sign in to comment.