diff --git a/packages/core/editor/package.json b/packages/core/editor/package.json index 357b32db8..644eb873a 100644 --- a/packages/core/editor/package.json +++ b/packages/core/editor/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/editor", - "version": "4.6.3", + "version": "4.6.4-rc.1", "license": "MIT", "private": false, "main": "dist/index.js", diff --git a/packages/core/editor/src/UI/Overlay/Overlay.tsx b/packages/core/editor/src/UI/Overlay/Overlay.tsx index 0a0cbb6b9..821571842 100644 --- a/packages/core/editor/src/UI/Overlay/Overlay.tsx +++ b/packages/core/editor/src/UI/Overlay/Overlay.tsx @@ -1,17 +1,18 @@ import { FloatingOverlay } from '@floating-ui/react'; -import { MouseEvent, ReactNode } from 'react'; +import { MouseEvent, ReactNode, forwardRef } from 'react'; type Props = { children: ReactNode; lockScroll?: boolean; className?: string; onClick?: (e: MouseEvent) => void; + style?: React.CSSProperties; }; -const Overlay = (props: Props) => { +const Overlay = ({ className, onClick, children, lockScroll = true, ...rest }: Props) => { return ( - - {props.children} + + {children} ); }; diff --git a/packages/core/editor/src/contexts/YooptaContext/YooptaContext.tsx b/packages/core/editor/src/contexts/YooptaContext/YooptaContext.tsx index cc7d64eae..d9d221bc4 100644 --- a/packages/core/editor/src/contexts/YooptaContext/YooptaContext.tsx +++ b/packages/core/editor/src/contexts/YooptaContext/YooptaContext.tsx @@ -11,7 +11,7 @@ const DEFAULT_HANDLERS: YooptaEditorContext = { editor: { id: '', - getBlock: () => undefined, + getBlock: () => null, insertBlock: () => undefined, insertBlocks: () => undefined, updateBlock: () => undefined, diff --git a/packages/core/editor/src/editor/elements/updateElement.ts b/packages/core/editor/src/editor/elements/updateElement.ts index dc0edc13c..e01385388 100644 --- a/packages/core/editor/src/editor/elements/updateElement.ts +++ b/packages/core/editor/src/editor/elements/updateElement.ts @@ -6,10 +6,9 @@ export type UpdateElementOptions = { path?: Path; }; -export type UpdateElement = { - type: TElementKeys; - props: TElementProps; -}; +export type UpdateElement = Partial< + Omit, 'id'> +>; export function updateElement( editor: YooEditor, @@ -37,12 +36,13 @@ export function updateElement( }); const elementToUpdate = elementEntry?.[0]; + const elementToUpdatePath = elementEntry?.[1]; const props = elementToUpdate?.props || {}; - const updatedNode = { props: { ...props, ...element.props } }; + const updatedElement = { props: { ...props, ...element.props } }; - Transforms.setNodes(slate, updatedNode, { - at: options?.path || [0], + Transforms.setNodes(slate, updatedElement, { + at: options?.path || elementToUpdatePath || [0], match: (n) => Element.isElement(n) && n.type === element.type, mode: 'lowest', }); diff --git a/packages/core/editor/src/parsers/deserializeHTML.ts b/packages/core/editor/src/parsers/deserializeHTML.ts index b94f3f122..4e90dcd5d 100644 --- a/packages/core/editor/src/parsers/deserializeHTML.ts +++ b/packages/core/editor/src/parsers/deserializeHTML.ts @@ -184,6 +184,8 @@ export function deserializeHTML(editor: YooEditor, html: HTMLElement) { console.log('pasted html', html); const PLUGINS_NODE_NAME_MATCHERS_MAP = getMappedPluginByNodeNames(editor); + console.log('PLUGINS_NODE_NAME_MATCHERS_MAP', PLUGINS_NODE_NAME_MATCHERS_MAP); + const blocks = deserialize(editor, PLUGINS_NODE_NAME_MATCHERS_MAP, html).filter(isYooptaBlock) as YooptaBlockData[]; return blocks; diff --git a/packages/core/editor/src/plugins/extenstions/withInlines.ts b/packages/core/editor/src/plugins/extenstions/withInlines.ts index 1eaedfd03..c39b9b2a3 100644 --- a/packages/core/editor/src/plugins/extenstions/withInlines.ts +++ b/packages/core/editor/src/plugins/extenstions/withInlines.ts @@ -36,7 +36,7 @@ const addLink = (editor, url: string) => { children: isCollapsed ? [{ text: url }] : [], props: { url, - target: '_blank', + target: '_self', rel: 'noopener noreferrer', }, } as SlateElement; diff --git a/packages/core/exports/package.json b/packages/core/exports/package.json index 1652b2674..3ec9966f9 100644 --- a/packages/core/exports/package.json +++ b/packages/core/exports/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/exports", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Serialize/deserialize exports in different formats for Yoopta-Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/development/src/pages/dev/index.tsx b/packages/development/src/pages/dev/index.tsx index 9ab9d01ae..10d81639c 100644 --- a/packages/development/src/pages/dev/index.tsx +++ b/packages/development/src/pages/dev/index.tsx @@ -52,15 +52,15 @@ const BasicExample = () => { tools={TOOLS} readOnly={readOnly} value={{ - '36048c70-ce45-4fe6-ad92-70ef6810b88c': { - id: '36048c70-ce45-4fe6-ad92-70ef6810b88c', + 'b5709c7d-c79b-4786-8d72-8a4d727163d4': { + id: 'b5709c7d-c79b-4786-8d72-8a4d727163d4', value: [ { - id: '66260b1b-1efd-4d8f-b6cd-3342480deea7', - type: 'heading-two', + id: '03bbf41c-d9c0-44ed-90d9-1bd17c66f94f', + type: 'heading-one', children: [ { - text: 'Built-in Constraints', + text: 'With custom renders ', }, ], props: { @@ -68,158 +68,97 @@ const BasicExample = () => { }, }, ], - type: 'HeadingTwo', + type: 'HeadingOne', meta: { order: 0, depth: 0, }, }, - '02fba3b4-f90e-4fe0-9284-9dff8cf5fa3b': { - id: '02fba3b4-f90e-4fe0-9284-9dff8cf5fa3b', + '1c07a2a9-9dba-4626-86f9-7fa0dd8c9f83': { + id: '1c07a2a9-9dba-4626-86f9-7fa0dd8c9f83', value: [ { - id: '5ae35463-0542-4f9a-b3b2-8f86dc8a132f', - type: 'accordion-list', + id: 'a5db0166-46c1-4170-94f9-12d758cdc882', + type: 'callout', children: [ { - id: 'c584e4fa-b735-4cd8-9c5e-a9f2ce765fbd', - type: 'accordion-list-item', - children: [ - { - id: '310aa032-aa99-42e1-b9d4-664a68c0c0ef', - type: 'accordion-list-item-heading', - children: [ - { - text: 'Built-in Constraints', - }, - ], - props: { - nodeType: 'block', - }, - }, - { - id: 'ad3fb9a9-1533-4080-82e1-c102a1e725c9', - type: 'accordion-list-item-content', - children: [ - { - text: 'Slate editors come with a few built-in constraints out of the box. These constraints are there to make working with content much more predictable than standard contenteditable. All of the built-in logic in Slate depends on these constraints, so unfortunately you cannot omit them. They are...', - }, - ], - props: { - nodeType: 'block', - }, - }, - ], - props: { - nodeType: 'block', - isExpanded: true, - }, + text: 'This example will show you how to add custom renders to plugins', }, + ], + props: { + theme: 'info', + }, + }, + ], + type: 'Callout', + meta: { + order: 1, + depth: 0, + }, + }, + '18cb86e2-44df-4d76-94e0-cf71b78d8ee8': { + id: '18cb86e2-44df-4d76-94e0-cf71b78d8ee8', + value: [ + { + id: '83810c57-3b88-4f94-95ff-632f5e8d6c3f', + type: 'heading-three', + children: [ { - id: 'caffa035-bfe1-40be-b5a6-e41fcd74f189', - type: 'accordion-list-item', - children: [ - { - id: '4c5e2f14-4c1f-4a78-803a-27fae2d1fb5d', - type: 'accordion-list-item-heading', - children: [ - { - text: 'Adding Constraints', - }, - ], - props: { - nodeType: 'block', - }, - }, - { - id: 'a1dc9763-e1fa-4e70-88b0-dfe335ae4344', - type: 'accordion-list-item-content', - children: [ - { - text: "All Element nodes must contain at least one Text descendant — even Void Elements. If an element node does not contain any children, an empty text node will be added as its only child. This constraint exists to ensure that the selection's anchor and focus points (which rely on referencing text nodes) can always be placed inside any node. Without this, empty elements (or void elements) wouldn't be selectable.", - }, - ], - props: { - nodeType: 'block', - }, - }, - ], - props: { - nodeType: 'block', - isExpanded: true, - }, + text: 'Example with ', }, { - id: '7a327ed3-b8d3-41a5-8292-64a2d82b57b6', - type: 'accordion-list-item', + text: '', + }, + { + id: 'c59655ae-fae4-472a-8fb3-e776e35395ad', + type: 'link', + props: { + url: '/', + target: '_self', + rel: 'noreferrer', + title: 'next/image', + nodeType: 'inline', + }, children: [ { - id: 'e7b5774a-6bd7-47a1-b8d2-dddedcb28418', - type: 'accordion-list-item-heading', - children: [ - { - text: 'Multi-pass Normalizing', - }, - ], - props: { - nodeType: 'block', - }, - }, - { - id: '093afe90-0e9a-4602-81f6-66c55abb7ad7', - type: 'accordion-list-item-content', - children: [ - { - text: "To do this, you extend the normalizeNode function on the editor. The normalizeNode function gets called every time an operation is applied that inserts or updates a node (or its descendants), giving you the opportunity to ensure that the changes didn't leave it in an invalid state, and correcting the node if so.", - }, - ], - props: { - nodeType: 'block', - }, + text: 'next/image', }, ], - props: { - nodeType: 'block', - isExpanded: true, - }, + }, + { + text: ' ', }, ], + props: { + nodeType: 'block', + }, }, ], - type: 'Accordion', + type: 'HeadingThree', meta: { - order: 1, + order: 2, depth: 0, }, }, - '226f5963-b01c-4b14-a7e8-45f3cf6c6b95': { - id: '226f5963-b01c-4b14-a7e8-45f3cf6c6b95', + 'ae04c7a1-fb94-4f0b-b428-3757a8d21196': { + id: 'ae04c7a1-fb94-4f0b-b428-3757a8d21196', value: [ { - id: 'dcfc9ee2-db6e-4127-b40c-83bcd465c5dd', - type: 'embed', - props: { - sizes: { - width: 650, - height: 400, - }, - nodeType: 'void', - provider: { - type: 'youtube', - id: 'bItAw5xgI4I', - url: 'https://www.youtube.com/watch?v=bItAw5xgI4I&t=468s', - }, - }, + id: '08a13b95-2371-4eac-9c30-83f1f8b72fc5', + type: 'callout', children: [ { - text: '', + text: "By default, the @yoopta/image plugin provides its own image rendering. \nBut what if you want to change the default rendering with powerful components like next/image, which provide top-level optimization and rendering with different layout. So, it's easy-peasy. ", }, ], + props: { + theme: 'default', + }, }, ], - type: 'Embed', + type: 'Callout', meta: { - order: 2, + order: 3, depth: 0, }, }, diff --git a/packages/development/src/pages/index.tsx b/packages/development/src/pages/index.tsx index 70d732e3f..34fa5461f 100644 --- a/packages/development/src/pages/index.tsx +++ b/packages/development/src/pages/index.tsx @@ -4,11 +4,15 @@ import { useRouter } from 'next/router'; const Index = () => { const router = useRouter(); - useEffect(() => { - router.push('/dev'); - }); + // useEffect(() => { + // router.push('/dev'); + // }); - return null; + return ( +
+

Some markup

+
+ ); }; export default Index; diff --git a/packages/development/src/utils/yoopta/plugins.tsx b/packages/development/src/utils/yoopta/plugins.tsx index 9c4fb545d..1380b5b35 100644 --- a/packages/development/src/utils/yoopta/plugins.tsx +++ b/packages/development/src/utils/yoopta/plugins.tsx @@ -37,18 +37,18 @@ export const YOOPTA_PLUGINS = [ }, }), File.extend({ - renders: { - file: ({ attributes, children, element }) => { - return ( - - ); - }, - }, + // renders: { + // file: ({ attributes, children, element }) => { + // return ( + // + // ); + // }, + // }, options: { onUpload: async (file: File) => { const data = await uploadToCloudinary(file, 'auto'); @@ -70,9 +70,9 @@ export const YOOPTA_PLUGINS = [ }, }), Image.extend({ - renders: { - image: YooptaWithNextImage, - }, + // renders: { + // image: YooptaWithNextImage, + // }, options: { maxSizes: { maxHeight: 750, maxWidth: 750 }, HTMLAttributes: { @@ -95,15 +95,6 @@ export const YOOPTA_PLUGINS = [ }, }), Headings.HeadingOne.extend({ - renders: { - 'heading-one': ({ attributes, children, element, blockId }) => { - return ( -

- {children} -

- ); - }, - }, options: { HTMLAttributes: { className: 'heading-one-element-extended', @@ -122,15 +113,6 @@ export const YOOPTA_PLUGINS = [ }), Headings.HeadingThree, Blockquote.extend({ - renders: { - blockquote: ({ attributes, children }) => { - return ( -
- {children} -
- ); - }, - }, options: { HTMLAttributes: { className: 'blockquote-element-extended', @@ -138,15 +120,6 @@ export const YOOPTA_PLUGINS = [ }, }), Callout.extend({ - renders: { - callout: ({ attributes, children }) => { - return ( -
- {children} -
- ); - }, - }, options: { HTMLAttributes: { className: 'callout-element-extended', @@ -161,50 +134,9 @@ export const YOOPTA_PLUGINS = [ }, }), Lists.NumberedList, - Lists.TodoList.extend({ - renders: { - 'todo-list': ({ attributes, children, element, blockId }) => { - const editor = useYooptaEditor(); - const { checked = false } = element.props; - const style = { - textDecoration: checked ? 'line-through' : 'none', - }; - - const onCheckedChange = (isChecked: boolean) => { - console.log('onCheckedChange', isChecked); - - Elements.updateElement(editor, blockId, { type: 'todo-list', props: { checked: isChecked } }); - }; - - return ( - - {children} - - ); - }, - }, - }), + Lists.TodoList, Embed, Video.extend({ - renders: { - video: ({ attributes, children, element }) => { - return ( -
-
- ); - }, - }, options: { HTMLAttributes: { className: 'video-element-extended', @@ -226,14 +158,26 @@ export const YOOPTA_PLUGINS = [ Link.extend({ renders: { link: ({ attributes, children, element }) => { + if (element.props.target === '_blank') { + return ( + + {children} + + ); + } + return ( {children} diff --git a/packages/marks/package.json b/packages/marks/package.json index 1f645e02c..e778c5343 100644 --- a/packages/marks/package.json +++ b/packages/marks/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/marks", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Marks for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/plugins/accordion/package.json b/packages/plugins/accordion/package.json index e604267f1..e85dd9bd3 100644 --- a/packages/plugins/accordion/package.json +++ b/packages/plugins/accordion/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/accordion", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Accordion plugin for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/plugins/blockquote/package.json b/packages/plugins/blockquote/package.json index b94df8c94..0f46b28fc 100644 --- a/packages/plugins/blockquote/package.json +++ b/packages/plugins/blockquote/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/blockquote", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Blockquote plugin for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/plugins/callout/package.json b/packages/plugins/callout/package.json index 3530a3252..0ab8e6d58 100644 --- a/packages/plugins/callout/package.json +++ b/packages/plugins/callout/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/callout", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Callout plugin for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/plugins/code/package.json b/packages/plugins/code/package.json index ddc78a56e..9e331fc08 100644 --- a/packages/plugins/code/package.json +++ b/packages/plugins/code/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/code", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Code plugin with syntax highlighting for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/plugins/embed/package.json b/packages/plugins/embed/package.json index 46275d51f..da374641c 100644 --- a/packages/plugins/embed/package.json +++ b/packages/plugins/embed/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/embed", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Embed plugin for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/plugins/file/package.json b/packages/plugins/file/package.json index ce399783c..ae34ff178 100644 --- a/packages/plugins/file/package.json +++ b/packages/plugins/file/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/file", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "File plugin for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/plugins/headings/package.json b/packages/plugins/headings/package.json index d378c11ed..059577f51 100644 --- a/packages/plugins/headings/package.json +++ b/packages/plugins/headings/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/headings", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Headings plugin for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/plugins/image/package.json b/packages/plugins/image/package.json index 87c9c51cf..11c4be268 100644 --- a/packages/plugins/image/package.json +++ b/packages/plugins/image/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/image", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Image plugin for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/plugins/link/package.json b/packages/plugins/link/package.json index e82bf2b2f..2d83c26ba 100644 --- a/packages/plugins/link/package.json +++ b/packages/plugins/link/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/link", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Link plugin for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", @@ -34,5 +34,7 @@ "bugs": { "url": "https://github.com/Darginec05/Editor-Yoopta/issues" }, - "gitHead": "29e4ae316ec75bb43d3822d028abcb0c34256ec5" + "dependencies": { + "lucide-react": "^0.379.0" + } } diff --git a/packages/plugins/link/src/plugin/index.tsx b/packages/plugins/link/src/plugin/index.tsx index de75e2af9..2f42d2655 100644 --- a/packages/plugins/link/src/plugin/index.tsx +++ b/packages/plugins/link/src/plugin/index.tsx @@ -9,7 +9,7 @@ const Link = new YooptaPlugin({ render: LinkRender, props: { url: null, - target: '_blank', + target: '_self', rel: 'noopener noreferrer', nodeType: 'inline', title: null, diff --git a/packages/plugins/link/src/styles.css b/packages/plugins/link/src/styles.css index 937cc515f..9619df4a5 100644 --- a/packages/plugins/link/src/styles.css +++ b/packages/plugins/link/src/styles.css @@ -1,5 +1,21 @@ @tailwind utilities; .yoopta-link { - @apply yoo-link-underline yoo-link-underline-offset-4 yoo-link-text-[#007AFF] hover:yoo-link-text-[#3b82f6] + @apply yoo-link-underline yoo-link-cursor-pointer yoo-link-underline-offset-4 yoo-link-relative yoo-link-text-[#007AFF] hover:yoo-link-text-[#3b82f6] +} + +.yoopta-link-preview { + @apply yoo-link-bg-[#FFFFFF] yoo-link-flex yoo-link-items-center yoo-link-z-50 yoo-link-p-[6px] yoo-link-rounded-md yoo-link-shadow-md yoo-link-border-[1px] yoo-link-border-solid yoo-link-border-[#e3e3e3] +} + +.yoopta-link-preview-text { + @apply yoo-link-text-sm yoo-link-max-w-[200px] yoo-link-select-none yoo-link-text-ellipsis yoo-link-overflow-hidden yoo-link-whitespace-nowrap +} + +.yoopta-link-preview-separator { + @apply yoo-link-w-[1px] yoo-link-h-[20px] yoo-link-bg-[#e3e3e3] yoo-link-mx-[5px] +} + +.yoopta-link-edit-button { + @apply yoo-link-text-sm yoo-link-cursor-pointer } \ No newline at end of file diff --git a/packages/plugins/link/src/ui/LinkHoverPreview.tsx b/packages/plugins/link/src/ui/LinkHoverPreview.tsx new file mode 100644 index 000000000..2ae938976 --- /dev/null +++ b/packages/plugins/link/src/ui/LinkHoverPreview.tsx @@ -0,0 +1,113 @@ +import { Elements, findSlateBySelectionPath, SlateElement, UI, useYooptaEditor, useYooptaTools } from '@yoopta/editor'; +import { Copy, SquareArrowOutUpRight } from 'lucide-react'; +import { useState } from 'react'; +import { useFloating, offset, flip, shift, inline, autoUpdate, useTransitionStyles } from '@floating-ui/react'; +import { LinkElementProps } from '../types'; +import { Editor, Element, Transforms } from 'slate'; + +const { Overlay, Portal } = UI; + +const LinkHoverPreview = ({ style, setFloating, element, setHoldLinkTool, blockId, onClose }) => { + const editor = useYooptaEditor(); + const tools = useYooptaTools(); + const [isEditLinkToolsOpen, setIsEditLinkToolsOpen] = useState(false); + + const { + refs: linkToolRefs, + floatingStyles: linkToolStyles, + context, + } = useFloating({ + placement: 'bottom', + open: isEditLinkToolsOpen, + onOpenChange: (open) => { + setIsEditLinkToolsOpen(open); + }, + middleware: [inline(), flip(), shift(), offset(10)], + whileElementsMounted: autoUpdate, + }); + + const { isMounted: isLinkToolMounted, styles: linkToolTransitionStyles } = useTransitionStyles(context, { + duration: { + open: 100, + close: 100, + }, + }); + + const linkToolEditStyles = { ...linkToolStyles, ...linkToolTransitionStyles, maxWidth: 400 }; + + const LinkTool = tools?.LinkTool; + const hasLinkTool = !!LinkTool; + + const onSave = (linkProps: LinkElementProps) => { + Elements.updateElement(editor, blockId, { + type: element.type, + props: { ...element.props, ...linkProps }, + }); + + setIsEditLinkToolsOpen(false); + onClose(); + }; + + const onDelete = () => { + const slate = findSlateBySelectionPath(editor); + const path = Elements.getElementPath(editor, blockId, element); + + if (!slate) return; + const linkNodeEntry = Elements.getElementEntry(editor, blockId, { path, type: element.type }); + + if (linkNodeEntry) { + Transforms.unwrapNodes(slate, { + match: (n) => !Editor.isEditor(n) && Element.isElement(n) && (n as SlateElement).type === element.type, + at: path || linkNodeEntry?.[1], + }); + } + }; + + const onOpenLink = () => { + window.open(element.props.url, element.props.target || '_self'); + }; + + return ( + + {isLinkToolMounted && hasLinkTool && ( + + +
e.stopPropagation()}> + +
+
+
+ )} +
+ {element.props.url} + + + + + + +
+
+ ); +}; + +export { LinkHoverPreview }; diff --git a/packages/plugins/link/src/ui/LinkRender.tsx b/packages/plugins/link/src/ui/LinkRender.tsx index ce049e65b..900b3c875 100644 --- a/packages/plugins/link/src/ui/LinkRender.tsx +++ b/packages/plugins/link/src/ui/LinkRender.tsx @@ -1,19 +1,40 @@ import { PluginElementRenderProps, useYooptaReadOnly } from '@yoopta/editor'; +import { useState } from 'react'; import { LinkElementProps } from '../types'; +import { LinkHoverPreview } from './LinkHoverPreview'; +import { useFloating, offset, flip, shift, inline, autoUpdate, useTransitionStyles } from '@floating-ui/react'; const VALID_TARGET_VALUES = ['_blank', '_self', '_parent', '_top', 'framename']; const LinkRender = ({ extendRender, ...props }: PluginElementRenderProps) => { + const [hovered, setHovered] = useState(false); + const [holdLinkTool, setHoldLinkTool] = useState(false); const { className = '', ...htmlAttrs } = props.HTMLAttributes || {}; + const { + refs: linkPreviewRefs, + floatingStyles: linkPreviewFloatingStyles, + context, + } = useFloating({ + placement: 'bottom', + open: hovered, + onOpenChange: setHovered, + middleware: [inline(), flip(), shift(), offset(0)], + whileElementsMounted: autoUpdate, + }); + + const { isMounted: isActionMenuMounted, styles: linkPreviewTransitionStyles } = useTransitionStyles(context, { + duration: { + open: 200, + close: 100, + }, + }); + + const linkPreviewStyles = { ...linkPreviewFloatingStyles, ...linkPreviewTransitionStyles }; + const { url, target = '', rel } = props.element.props || {}; const isReadOnly = useYooptaReadOnly(); - const onClick = (e) => { - if (isReadOnly) return; - e.preventDefault(); - }; - const linkProps: Partial> = { rel, target, @@ -27,22 +48,47 @@ const LinkRender = ({ extendRender, ...props }: PluginElementRenderProps) => { delete linkProps.rel; } - if (extendRender) { - return extendRender(props); - } + const onClose = () => { + setHoldLinkTool(false); + setHovered(false); + }; + + const onMouseOver = () => { + setHovered(true); + }; + + const onMouseOut = () => { + if (holdLinkTool) return; + onClose(); + }; return ( - - {props.children} - + + {extendRender ? ( + extendRender(props) + ) : ( + + {props.children} + + )} + {isActionMenuMounted && !isReadOnly && ( + + )} + ); }; diff --git a/packages/plugins/lists/package.json b/packages/plugins/lists/package.json index 9b72ec5b9..8d9981ed3 100644 --- a/packages/plugins/lists/package.json +++ b/packages/plugins/lists/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/lists", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Lists plugin for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/plugins/paragraph/package.json b/packages/plugins/paragraph/package.json index 8561f129a..4afef6aa8 100644 --- a/packages/plugins/paragraph/package.json +++ b/packages/plugins/paragraph/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/paragraph", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Paragraph plugin for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/plugins/video/package.json b/packages/plugins/video/package.json index 48313bd07..4b672b2e3 100644 --- a/packages/plugins/video/package.json +++ b/packages/plugins/video/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/video", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Video plugin for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/tools/action-menu/package.json b/packages/tools/action-menu/package.json index ae4f30ccf..26a7e5464 100644 --- a/packages/tools/action-menu/package.json +++ b/packages/tools/action-menu/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/action-menu-list", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "ActionMenuList tool for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/tools/link-tool/package.json b/packages/tools/link-tool/package.json index 4fe3ef0ab..47afd754c 100644 --- a/packages/tools/link-tool/package.json +++ b/packages/tools/link-tool/package.json @@ -1,6 +1,6 @@ { "name": "@yoopta/link-tool", - "version": "4.6.3", + "version": "4.6.4-rc.1", "description": "Link tool for Yoopta Editor", "author": "Darginec05 ", "homepage": "https://github.com/Darginec05/Editor-Yoopta#readme", diff --git a/packages/tools/link-tool/src/components/DefaultLinkToolRender.tsx b/packages/tools/link-tool/src/components/DefaultLinkToolRender.tsx index 5a8bd27a2..f803d3d70 100644 --- a/packages/tools/link-tool/src/components/DefaultLinkToolRender.tsx +++ b/packages/tools/link-tool/src/components/DefaultLinkToolRender.tsx @@ -5,22 +5,14 @@ import ChevronUp from '../icons/chevronup.svg'; const DEFAULT_LINK_VALUE: Link = { url: '', title: '', - target: '_blank', + target: '_self', rel: 'noreferrer', }; -function isUrl(string: any): boolean { - try { - new URL(string); - return true; - } catch (_) { - return false; - } -} - const DefaultLinkToolRender = (props: LinkToolRenderProps) => { + const { withLink = true, withTitle = true } = props; const [link, setLink] = useState(props?.link || DEFAULT_LINK_VALUE); - const [additionalPropsOpen, setAdditionPropsOpen] = useState(false); + const [isAdditionalPropsOpen, setAdditionPropsOpen] = useState(false); const onChange = (e: ChangeEvent) => { const target = e.target as HTMLInputElement; @@ -42,45 +34,49 @@ const DefaultLinkToolRender = (props: LinkToolRenderProps) => { return (
-
- - -
-
- - -
+ {withTitle && ( +
+ + +
+ )} + {withLink && ( +
+ + +
+ )} - {additionalPropsOpen && ( + {isAdditionalPropsOpen && ( <>